about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.env.nanobox95
-rw-r--r--.env.production.sample83
-rw-r--r--.env.test1
-rw-r--r--.travis.yml33
-rw-r--r--AUTHORS.md450
-rw-r--r--Dockerfile34
-rw-r--r--Gemfile23
-rw-r--r--Gemfile.lock114
-rw-r--r--Vagrantfile5
-rw-r--r--app/chewy/statuses_index.rb61
-rw-r--r--app/controllers/about_controller.rb2
-rw-r--r--app/controllers/accounts_controller.rb36
-rw-r--r--app/controllers/activitypub/collections_controller.rb57
-rw-r--r--app/controllers/activitypub/inboxes_controller.rb4
-rw-r--r--app/controllers/activitypub/outboxes_controller.rb6
-rw-r--r--app/controllers/admin/settings_controller.rb4
-rw-r--r--app/controllers/api/base_controller.rb4
-rw-r--r--app/controllers/api/salmon_controller.rb4
-rw-r--r--app/controllers/api/v1/accounts/credentials_controller.rb2
-rw-r--r--app/controllers/api/v1/accounts/relationships_controller.rb4
-rw-r--r--app/controllers/api/v1/accounts/search_controller.rb4
-rw-r--r--app/controllers/api/v1/accounts/statuses_controller.rb14
-rw-r--r--app/controllers/api/v1/accounts_controller.rb6
-rw-r--r--app/controllers/api/v1/media_controller.rb2
-rw-r--r--app/controllers/api/v1/reports_controller.rb12
-rw-r--r--app/controllers/api/v1/search_controller.rb6
-rw-r--r--app/controllers/api/v1/statuses/pins_controller.rb28
-rw-r--r--app/controllers/api/v1/timelines/public_controller.rb14
-rw-r--r--app/controllers/api/v1/timelines/tag_controller.rb14
-rw-r--r--app/controllers/application_controller.rb7
-rw-r--r--app/controllers/auth/confirmations_controller.rb23
-rw-r--r--app/controllers/auth/omniauth_callbacks_controller.rb33
-rw-r--r--app/controllers/auth/registrations_controller.rb5
-rw-r--r--app/controllers/auth/sessions_controller.rb22
-rw-r--r--app/controllers/authorize_follows_controller.rb2
-rw-r--r--app/controllers/concerns/localized.rb12
-rw-r--r--app/controllers/concerns/signature_authentication.rb11
-rw-r--r--app/controllers/concerns/signature_verification.rb2
-rw-r--r--app/controllers/concerns/user_tracking_concern.rb16
-rw-r--r--app/controllers/follower_accounts_controller.rb2
-rw-r--r--app/controllers/following_accounts_controller.rb2
-rw-r--r--app/controllers/home_controller.rb3
-rw-r--r--app/controllers/media_controller.rb18
-rw-r--r--app/controllers/settings/exports_controller.rb14
-rw-r--r--app/controllers/settings/preferences_controller.rb1
-rw-r--r--app/controllers/settings/two_factor_authentication/confirmations_controller.rb6
-rw-r--r--app/controllers/statuses_controller.rb1
-rw-r--r--app/controllers/stream_entries_controller.rb5
-rw-r--r--app/helpers/application_helper.rb10
-rw-r--r--app/helpers/instance_helper.rb2
-rw-r--r--app/helpers/settings_helper.rb1
-rw-r--r--app/helpers/stream_entries_helper.rb21
-rw-r--r--app/javascript/core/admin.js8
-rw-r--r--app/javascript/flavours/glitch/components/intersection_observer_article.js26
-rw-r--r--app/javascript/flavours/glitch/components/status.js3
-rw-r--r--app/javascript/flavours/glitch/features/account/components/header.js19
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/components/header.js1
-rw-r--r--app/javascript/flavours/glitch/styles/about.scss813
-rw-r--r--app/javascript/flavours/glitch/styles/basics.scss1
-rw-r--r--app/javascript/flavours/glitch/styles/components/accounts.scss16
-rw-r--r--app/javascript/flavours/glitch/styles/components/columns.scss17
-rw-r--r--app/javascript/flavours/glitch/styles/components/index.scss22
-rw-r--r--app/javascript/flavours/glitch/styles/components/media.scss4
-rw-r--r--app/javascript/flavours/glitch/styles/containers.scss2
-rw-r--r--app/javascript/images/icon_file_download.svg4
-rw-r--r--app/javascript/images/mailer/icon_cached.pngbin754 -> 582 bytes
-rw-r--r--app/javascript/images/mailer/icon_file_download.pngbin0 -> 271 bytes
-rw-r--r--app/javascript/images/reticle.pngbin0 -> 3053 bytes
-rw-r--r--app/javascript/mastodon/actions/compose.js73
-rw-r--r--app/javascript/mastodon/actions/dropdown_menu.js10
-rw-r--r--app/javascript/mastodon/actions/push_notifications/registerer.js9
-rw-r--r--app/javascript/mastodon/actions/reports.js9
-rw-r--r--app/javascript/mastodon/actions/search.js2
-rw-r--r--app/javascript/mastodon/actions/statuses.js25
-rw-r--r--app/javascript/mastodon/actions/store.js13
-rw-r--r--app/javascript/mastodon/actions/timelines.js17
-rw-r--r--app/javascript/mastodon/base_polyfills.js5
-rw-r--r--app/javascript/mastodon/components/attachment_list.js36
-rw-r--r--app/javascript/mastodon/components/autosuggest_textarea.js5
-rw-r--r--app/javascript/mastodon/components/column_back_button_slim.js13
-rw-r--r--app/javascript/mastodon/components/column_header.js26
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js55
-rw-r--r--app/javascript/mastodon/components/extended_video_player.js8
-rw-r--r--app/javascript/mastodon/components/media_gallery.js96
-rw-r--r--app/javascript/mastodon/components/permalink.js10
-rw-r--r--app/javascript/mastodon/components/scrollable_list.js12
-rw-r--r--app/javascript/mastodon/components/status.js36
-rw-r--r--app/javascript/mastodon/components/status_content.js11
-rw-r--r--app/javascript/mastodon/components/status_list.js21
-rw-r--r--app/javascript/mastodon/containers/dropdown_menu_container.js19
-rw-r--r--app/javascript/mastodon/containers/status_container.js16
-rw-r--r--app/javascript/mastodon/containers/timeline_container.js12
-rw-r--r--app/javascript/mastodon/features/account/components/action_bar.js6
-rw-r--r--app/javascript/mastodon/features/account/components/header.js18
-rw-r--r--app/javascript/mastodon/features/account_gallery/components/media_item.js44
-rw-r--r--app/javascript/mastodon/features/account_gallery/index.js5
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/header.js14
-rw-r--r--app/javascript/mastodon/features/account_timeline/index.js40
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js7
-rw-r--r--app/javascript/mastodon/features/compose/components/search.js5
-rw-r--r--app/javascript/mastodon/features/compose/components/search_results.js6
-rw-r--r--app/javascript/mastodon/features/compose/components/upload.js20
-rw-r--r--app/javascript/mastodon/features/compose/containers/compose_form_container.js1
-rw-r--r--app/javascript/mastodon/features/compose/containers/upload_container.js7
-rw-r--r--app/javascript/mastodon/features/compose/containers/warning_container.js2
-rw-r--r--app/javascript/mastodon/features/compose/index.js7
-rw-r--r--app/javascript/mastodon/features/favourited_statuses/index.js4
-rw-r--r--app/javascript/mastodon/features/followers/index.js2
-rw-r--r--app/javascript/mastodon/features/following/index.js2
-rw-r--r--app/javascript/mastodon/features/getting_started/index.js37
-rw-r--r--app/javascript/mastodon/features/notifications/components/clear_column_button.js2
-rw-r--r--app/javascript/mastodon/features/notifications/index.js10
-rw-r--r--app/javascript/mastodon/features/report/components/status_check_box.js44
-rw-r--r--app/javascript/mastodon/features/standalone/community_timeline/index.js74
-rw-r--r--app/javascript/mastodon/features/status/components/card.js175
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js8
-rw-r--r--app/javascript/mastodon/features/status/index.js40
-rw-r--r--app/javascript/mastodon/features/ui/components/__tests__/column-test.js4
-rw-r--r--app/javascript/mastodon/features/ui/components/bundle.js13
-rw-r--r--app/javascript/mastodon/features/ui/components/bundle_column_error.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/bundle_modal_error.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/column_header.js20
-rw-r--r--app/javascript/mastodon/features/ui/components/column_link.js7
-rw-r--r--app/javascript/mastodon/features/ui/components/columns_area.js17
-rw-r--r--app/javascript/mastodon/features/ui/components/focal_point_modal.js122
-rw-r--r--app/javascript/mastodon/features/ui/components/image_loader.js32
-rw-r--r--app/javascript/mastodon/features/ui/components/media_modal.js84
-rw-r--r--app/javascript/mastodon/features/ui/components/modal_root.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/report_modal.js47
-rw-r--r--app/javascript/mastodon/features/ui/components/tabs_bar.js15
-rw-r--r--app/javascript/mastodon/features/ui/components/video_modal.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/zoomable_image.js151
-rw-r--r--app/javascript/mastodon/features/ui/containers/status_list_container.js5
-rw-r--r--app/javascript/mastodon/features/ui/index.js164
-rw-r--r--app/javascript/mastodon/features/ui/util/react_router_helpers.js11
-rw-r--r--app/javascript/mastodon/features/video/index.js48
-rw-r--r--app/javascript/mastodon/initial_state.js2
-rw-r--r--app/javascript/mastodon/load_polyfills.js1
-rw-r--r--app/javascript/mastodon/locales/ar.json26
-rw-r--r--app/javascript/mastodon/locales/bg.json20
-rw-r--r--app/javascript/mastodon/locales/ca.json98
-rw-r--r--app/javascript/mastodon/locales/de.json68
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json120
-rw-r--r--app/javascript/mastodon/locales/en.json19
-rw-r--r--app/javascript/mastodon/locales/eo.json312
-rw-r--r--app/javascript/mastodon/locales/es.json24
-rw-r--r--app/javascript/mastodon/locales/fa.json20
-rw-r--r--app/javascript/mastodon/locales/fi.json388
-rw-r--r--app/javascript/mastodon/locales/fr.json68
-rw-r--r--app/javascript/mastodon/locales/gl.json30
-rw-r--r--app/javascript/mastodon/locales/he.json20
-rw-r--r--app/javascript/mastodon/locales/hr.json20
-rw-r--r--app/javascript/mastodon/locales/hu.json418
-rw-r--r--app/javascript/mastodon/locales/hy.json278
-rw-r--r--app/javascript/mastodon/locales/id.json20
-rw-r--r--app/javascript/mastodon/locales/io.json20
-rw-r--r--app/javascript/mastodon/locales/it.json20
-rw-r--r--app/javascript/mastodon/locales/ja.json74
-rw-r--r--app/javascript/mastodon/locales/ko.json30
-rw-r--r--app/javascript/mastodon/locales/nl.json34
-rw-r--r--app/javascript/mastodon/locales/no.json122
-rw-r--r--app/javascript/mastodon/locales/oc.json72
-rw-r--r--app/javascript/mastodon/locales/pl.json28
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json26
-rw-r--r--app/javascript/mastodon/locales/pt.json26
-rw-r--r--app/javascript/mastodon/locales/ru.json106
-rw-r--r--app/javascript/mastodon/locales/sk.json280
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json20
-rw-r--r--app/javascript/mastodon/locales/sr.json20
-rw-r--r--app/javascript/mastodon/locales/sv.json98
-rw-r--r--app/javascript/mastodon/locales/th.json20
-rw-r--r--app/javascript/mastodon/locales/tr.json20
-rw-r--r--app/javascript/mastodon/locales/uk.json20
-rw-r--r--app/javascript/mastodon/locales/whitelist_hy.json2
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json14
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json20
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json20
-rw-r--r--app/javascript/mastodon/reducers/accounts.js2
-rw-r--r--app/javascript/mastodon/reducers/compose.js30
-rw-r--r--app/javascript/mastodon/reducers/dropdown_menu.js18
-rw-r--r--app/javascript/mastodon/reducers/index.js2
-rw-r--r--app/javascript/mastodon/reducers/reports.js4
-rw-r--r--app/javascript/mastodon/reducers/statuses.js17
-rw-r--r--app/javascript/mastodon/settings.js1
-rw-r--r--app/javascript/styles/mastodon/about.scss841
-rw-r--r--app/javascript/styles/mastodon/accounts.scss83
-rw-r--r--app/javascript/styles/mastodon/admin.scss10
-rw-r--r--app/javascript/styles/mastodon/basics.scss5
-rw-r--r--app/javascript/styles/mastodon/components.scss756
-rw-r--r--app/javascript/styles/mastodon/containers.scss2
-rw-r--r--app/javascript/styles/mastodon/forms.scss33
-rw-r--r--app/javascript/styles/mastodon/stream_entries.scss24
-rw-r--r--app/lib/activitypub/activity.rb8
-rw-r--r--app/lib/activitypub/activity/add.rb13
-rw-r--r--app/lib/activitypub/activity/announce.rb5
-rw-r--r--app/lib/activitypub/activity/create.rb17
-rw-r--r--app/lib/activitypub/activity/flag.rb25
-rw-r--r--app/lib/activitypub/activity/reject.rb2
-rw-r--r--app/lib/activitypub/activity/remove.rb14
-rw-r--r--app/lib/activitypub/adapter.rb2
-rw-r--r--app/lib/exceptions.rb1
-rw-r--r--app/lib/fast_geometry_parser.rb11
-rw-r--r--app/lib/feed_manager.rb25
-rw-r--r--app/lib/formatter.rb2
-rw-r--r--app/lib/frontmatter_handler.rb21
-rw-r--r--app/lib/ostatus/activity/creation.rb15
-rw-r--r--app/lib/request.rb19
-rw-r--r--app/lib/sidekiq_error_handler.rb11
-rw-r--r--app/lib/status_filter.rb1
-rw-r--r--app/lib/user_settings_decorator.rb31
-rw-r--r--app/mailers/application_mailer.rb1
-rw-r--r--app/mailers/notification_mailer.rb6
-rw-r--r--app/mailers/user_mailer.rb13
-rw-r--r--app/models/account.rb3
-rw-r--r--app/models/account_domain_block.rb8
-rw-r--r--app/models/backup.rb22
-rw-r--r--app/models/block.rb4
-rw-r--r--app/models/concerns/account_avatar.rb6
-rw-r--r--app/models/concerns/account_header.rb6
-rw-r--r--app/models/concerns/omniauthable.rb85
-rw-r--r--app/models/concerns/paginable.rb9
-rw-r--r--app/models/concerns/relationship_cacheable.rb16
-rw-r--r--app/models/concerns/remotable.rb6
-rw-r--r--app/models/favourite.rb2
-rw-r--r--app/models/follow.rb1
-rw-r--r--app/models/follow_request.rb1
-rw-r--r--app/models/form/admin_settings.rb2
-rw-r--r--app/models/form/migration.rb2
-rw-r--r--app/models/glitch/keyword_mute_helper.rb27
-rw-r--r--app/models/identity.rb22
-rw-r--r--app/models/import.rb2
-rw-r--r--app/models/invite.rb2
-rw-r--r--app/models/media_attachment.rb74
-rw-r--r--app/models/mute.rb4
-rw-r--r--app/models/notification.rb3
-rw-r--r--app/models/preview_card.rb13
-rw-r--r--app/models/report.rb4
-rw-r--r--app/models/site_upload.rb4
-rw-r--r--app/models/status.rb34
-rw-r--r--app/models/user.rb117
-rw-r--r--app/models/web/setting.rb2
-rw-r--r--app/policies/application_policy.rb4
-rw-r--r--app/policies/backup_policy.rb9
-rw-r--r--app/presenters/account_relationships_presenter.rb68
-rw-r--r--app/presenters/instance_presenter.rb4
-rw-r--r--app/serializers/activitypub/actor_serializer.rb6
-rw-r--r--app/serializers/activitypub/add_serializer.rb24
-rw-r--r--app/serializers/activitypub/collection_serializer.rb6
-rw-r--r--app/serializers/activitypub/flag_serializer.rb27
-rw-r--r--app/serializers/activitypub/image_serializer.rb9
-rw-r--r--app/serializers/activitypub/note_serializer.rb9
-rw-r--r--app/serializers/activitypub/outbox_serializer.rb8
-rw-r--r--app/serializers/activitypub/remove_serializer.rb24
-rw-r--r--app/serializers/initial_state_serializer.rb16
-rw-r--r--app/serializers/rest/instance_serializer.rb11
-rw-r--r--app/services/account_search_service.rb2
-rw-r--r--app/services/activitypub/fetch_featured_collection_service.rb52
-rw-r--r--app/services/activitypub/process_account_service.rb22
-rw-r--r--app/services/backup_service.rb128
-rw-r--r--app/services/block_domain_service.rb37
-rw-r--r--app/services/concerns/author_extractor.rb2
-rw-r--r--app/services/fetch_link_card_service.rb26
-rw-r--r--app/services/follow_service.rb2
-rw-r--r--app/services/post_status_service.rb10
-rw-r--r--app/services/process_mentions_service.rb6
-rw-r--r--app/services/report_service.rb54
-rw-r--r--app/services/resolve_account_service.rb (renamed from app/services/resolve_remote_account_service.rb)2
-rw-r--r--app/services/resolve_url_service.rb (renamed from app/services/fetch_remote_resource_service.rb)2
-rw-r--r--app/services/search_service.rb59
-rw-r--r--app/validators/status_length_validator.rb2
-rw-r--r--app/validators/unreserved_username_validator.rb6
-rw-r--r--app/views/about/_forms.html.haml15
-rw-r--r--app/views/about/_links.html.haml16
-rw-r--r--app/views/about/_registration.html.haml2
-rw-r--r--app/views/about/more.html.haml33
-rw-r--r--app/views/about/show.html.haml192
-rw-r--r--app/views/about/terms.html.haml16
-rw-r--r--app/views/accounts/_follow_button.html.haml23
-rw-r--r--app/views/accounts/_grid_card.html.haml7
-rw-r--r--app/views/accounts/_header.html.haml19
-rw-r--r--app/views/accounts/_og.html.haml2
-rw-r--r--app/views/accounts/show.html.haml16
-rw-r--r--app/views/admin/accounts/show.html.haml5
-rw-r--r--app/views/admin/settings/edit.html.haml4
-rw-r--r--app/views/auth/confirmations/finish_signup.html.haml14
-rw-r--r--app/views/auth/passwords/edit.html.haml14
-rw-r--r--app/views/auth/registrations/_sessions.html.haml2
-rw-r--r--app/views/auth/registrations/edit.html.haml23
-rw-r--r--app/views/auth/sessions/new.html.haml14
-rw-r--r--app/views/authorize_follows/success.html.haml2
-rw-r--r--app/views/follower_accounts/index.html.haml4
-rw-r--r--app/views/following_accounts/index.html.haml4
-rwxr-xr-xapp/views/layouts/application.html.haml8
-rw-r--r--app/views/layouts/auth.html.haml2
-rw-r--r--app/views/layouts/mailer.html.haml2
-rw-r--r--app/views/layouts/modal.html.haml2
-rw-r--r--app/views/layouts/public.html.haml2
-rw-r--r--app/views/media/player.html.haml2
-rw-r--r--app/views/notification_mailer/_status.html.haml7
-rw-r--r--app/views/settings/exports/show.html.haml23
-rw-r--r--app/views/settings/preferences/show.html.haml4
-rw-r--r--app/views/stream_entries/_detailed_status.html.haml4
-rw-r--r--app/views/stream_entries/_og_description.html.haml5
-rw-r--r--app/views/stream_entries/_og_image.html.haml29
-rw-r--r--app/views/stream_entries/_simple_status.html.haml5
-rw-r--r--app/views/stream_entries/show.html.haml2
-rw-r--r--app/views/tags/_features.html.haml25
-rw-r--r--app/views/tags/show.html.haml61
-rw-r--r--app/views/user_mailer/backup_ready.html.haml59
-rw-r--r--app/views/user_mailer/backup_ready.text.erb7
-rw-r--r--app/workers/activitypub/synchronize_featured_collection_worker.rb13
-rw-r--r--app/workers/backup_worker.rb17
-rw-r--r--app/workers/import/relationship_worker.rb2
-rw-r--r--app/workers/resolve_account_worker.rb (renamed from app/workers/resolve_remote_account_worker.rb)4
-rw-r--r--app/workers/scheduler/backup_cleanup_scheduler.rb16
-rw-r--r--boxfile.yml25
-rw-r--r--config/application.rb12
-rw-r--r--config/brakeman.ignore86
-rw-r--r--config/environments/development.rb8
-rw-r--r--config/environments/production.rb6
-rw-r--r--config/environments/test.rb9
-rw-r--r--config/i18n-tasks.yml1
-rw-r--r--config/initializers/chewy.rb24
-rw-r--r--config/initializers/devise.rb59
-rw-r--r--config/initializers/omniauth.rb66
-rw-r--r--config/initializers/sidekiq.rb4
-rw-r--r--config/initializers/twitter_regex.rb2
-rw-r--r--config/locales/activerecord.ar.yml4
-rw-r--r--config/locales/activerecord.es.yml13
-rw-r--r--config/locales/ar.yml125
-rw-r--r--config/locales/bg.yml2
-rw-r--r--config/locales/ca.yml244
-rw-r--r--config/locales/de.yml129
-rw-r--r--config/locales/devise.ar.yml36
-rw-r--r--config/locales/devise.ca.yml14
-rw-r--r--config/locales/devise.de.yml27
-rw-r--r--config/locales/devise.eo.yml93
-rw-r--r--config/locales/devise.es.yml31
-rw-r--r--config/locales/devise.fr.yml35
-rw-r--r--config/locales/devise.gl.yml17
-rw-r--r--config/locales/devise.hr.yml3
-rw-r--r--config/locales/devise.hu.yml23
-rw-r--r--config/locales/devise.nl.yml17
-rw-r--r--config/locales/devise.no.yml21
-rw-r--r--config/locales/devise.oc.yml35
-rw-r--r--config/locales/devise.pt-BR.yml19
-rw-r--r--config/locales/devise.pt.yml23
-rw-r--r--config/locales/devise.ru.yml25
-rw-r--r--config/locales/devise.sk.yml41
-rw-r--r--config/locales/devise.sv.yml23
-rw-r--r--config/locales/devise.tr.yml2
-rw-r--r--config/locales/doorkeeper.ar.yml18
-rw-r--r--config/locales/doorkeeper.eo.yml54
-rw-r--r--config/locales/doorkeeper.es.yml2
-rw-r--r--config/locales/doorkeeper.fr.yml4
-rw-r--r--config/locales/doorkeeper.hu.yml7
-rw-r--r--config/locales/doorkeeper.no.yml2
-rw-r--r--config/locales/doorkeeper.ru.yml6
-rw-r--r--config/locales/doorkeeper.sk.yml6
-rw-r--r--config/locales/doorkeeper.sv.yml8
-rw-r--r--config/locales/en.yml31
-rw-r--r--config/locales/eo.yml777
-rw-r--r--config/locales/es.yml58
-rw-r--r--config/locales/fa.yml3
-rw-r--r--config/locales/fi.yml44
-rw-r--r--config/locales/fr.yml84
-rw-r--r--config/locales/gl.yml76
-rw-r--r--config/locales/he.yml3
-rw-r--r--config/locales/hr.yml2
-rw-r--r--config/locales/hu.yml704
-rw-r--r--config/locales/id.yml2
-rw-r--r--config/locales/io.yml2
-rw-r--r--config/locales/it.yml2
-rw-r--r--config/locales/ja.yml65
-rw-r--r--config/locales/ko.yml236
-rw-r--r--config/locales/nl.yml86
-rw-r--r--config/locales/no.yml361
-rw-r--r--config/locales/oc.yml63
-rw-r--r--config/locales/pl.yml34
-rw-r--r--config/locales/pt-BR.yml68
-rw-r--r--config/locales/pt.yml37
-rw-r--r--config/locales/ru.yml3
-rw-r--r--config/locales/simple_form.ar.yml80
-rw-r--r--config/locales/simple_form.bg.yml2
-rw-r--r--config/locales/simple_form.ca.yml76
-rw-r--r--config/locales/simple_form.de.yml4
-rw-r--r--config/locales/simple_form.en.yml4
-rw-r--r--config/locales/simple_form.eo.yml69
-rw-r--r--config/locales/simple_form.es.yml4
-rw-r--r--config/locales/simple_form.fi.yml2
-rw-r--r--config/locales/simple_form.fr.yml6
-rw-r--r--config/locales/simple_form.gl.yml6
-rw-r--r--config/locales/simple_form.he.yml2
-rw-r--r--config/locales/simple_form.hr.yml2
-rw-r--r--config/locales/simple_form.hu.yml44
-rw-r--r--config/locales/simple_form.id.yml2
-rw-r--r--config/locales/simple_form.io.yml2
-rw-r--r--config/locales/simple_form.it.yml2
-rw-r--r--config/locales/simple_form.ja.yml4
-rw-r--r--config/locales/simple_form.ko.yml8
-rw-r--r--config/locales/simple_form.nl.yml8
-rw-r--r--config/locales/simple_form.no.yml56
-rw-r--r--config/locales/simple_form.oc.yml4
-rw-r--r--config/locales/simple_form.pl.yml4
-rw-r--r--config/locales/simple_form.pt-BR.yml4
-rw-r--r--config/locales/simple_form.pt.yml75
-rw-r--r--config/locales/simple_form.ru.yml7
-rw-r--r--config/locales/simple_form.sk.yml32
-rw-r--r--config/locales/simple_form.sr-Latn.yml2
-rw-r--r--config/locales/simple_form.sr.yml2
-rw-r--r--config/locales/simple_form.sv.yml17
-rw-r--r--config/locales/simple_form.th.yml2
-rw-r--r--config/locales/simple_form.tr.yml2
-rw-r--r--config/locales/simple_form.uk.yml2
-rw-r--r--config/locales/simple_form.zh-CN.yml2
-rw-r--r--config/locales/simple_form.zh-HK.yml2
-rw-r--r--config/locales/simple_form.zh-TW.yml2
-rw-r--r--config/locales/sk.yml445
-rw-r--r--config/locales/sr-Latn.yml3
-rw-r--r--config/locales/sr.yml3
-rw-r--r--config/locales/sv.yml257
-rw-r--r--config/locales/th.yml2
-rw-r--r--config/locales/tr.yml2
-rw-r--r--config/locales/uk.yml2
-rw-r--r--config/locales/zh-CN.yml3
-rw-r--r--config/locales/zh-HK.yml2
-rw-r--r--config/locales/zh-TW.yml2
-rw-r--r--config/navigation.rb2
-rw-r--r--config/routes.rb11
-rw-r--r--config/settings.yml2
-rw-r--r--config/sidekiq.yml3
-rw-r--r--config/webpack/configuration.js2
-rw-r--r--db/migrate/20180109143959_add_remember_token_to_users.rb5
-rw-r--r--db/migrate/20180204034416_create_identities.rb11
-rw-r--r--db/migrate/20180206000000_change_user_id_nonnullable.rb6
-rw-r--r--db/migrate/20180211015820_create_backups.rb11
-rw-r--r--db/migrate/20180304013859_add_featured_collection_url_to_accounts.rb5
-rw-r--r--db/schema.rb31
-rw-r--r--docker-compose.yml39
-rw-r--r--docker_entrypoint.sh14
-rw-r--r--lib/devise/ldap_authenticatable.rb49
-rw-r--r--lib/mastodon/premailer_webpack_strategy.rb19
-rw-r--r--lib/mastodon/version.rb4
-rw-r--r--lib/paperclip/gif_transcoder.rb2
-rw-r--r--lib/paperclip/lazy_thumbnail.rb28
-rw-r--r--lib/tasks/assets.rake8
-rw-r--r--lib/tasks/mastodon.rake432
-rw-r--r--nanobox/nginx-web.conf.erb8
-rw-r--r--package.json9
-rw-r--r--spec/controllers/api/salmon_controller_spec.rb16
-rw-r--r--spec/controllers/api/v1/accounts/relationships_controller_spec.rb22
-rw-r--r--spec/controllers/authorize_follows_controller_spec.rb4
-rw-r--r--spec/controllers/concerns/localized_spec.rb53
-rw-r--r--spec/controllers/home_controller_spec.rb15
-rw-r--r--spec/controllers/settings/imports_controller_spec.rb4
-rw-r--r--spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb7
-rw-r--r--spec/fabricators/backup_fabricator.rb3
-rw-r--r--spec/fabricators/identity_fabricator.rb5
-rw-r--r--spec/fabricators/list_fabricator.rb2
-rw-r--r--spec/fixtures/requests/webfinger-hacker3.txt11
-rw-r--r--spec/helpers/instance_helper_spec.rb6
-rw-r--r--spec/lib/activitypub/activity/add_spec.rb29
-rw-r--r--spec/lib/activitypub/activity/create_spec.rb29
-rw-r--r--spec/lib/activitypub/activity/flag_spec.rb37
-rw-r--r--spec/lib/activitypub/activity/remove_spec.rb30
-rw-r--r--spec/lib/activitypub/activity/update_spec.rb1
-rw-r--r--spec/lib/feed_manager_spec.rb44
-rw-r--r--spec/lib/request_spec.rb31
-rw-r--r--spec/mailers/notification_mailer_spec.rb2
-rw-r--r--spec/mailers/previews/user_mailer_preview.rb5
-rw-r--r--spec/models/account_spec.rb8
-rw-r--r--spec/models/backup_spec.rb5
-rw-r--r--spec/models/glitch/keyword_mute_helper_spec.rb50
-rw-r--r--spec/models/identity_spec.rb5
-rw-r--r--spec/models/media_attachment_spec.rb1
-rw-r--r--spec/models/setting_spec.rb15
-rw-r--r--spec/presenters/instance_presenter_spec.rb12
-rw-r--r--spec/rails_helper.rb2
-rw-r--r--spec/services/account_search_service_spec.rb4
-rw-r--r--spec/services/fetch_atom_service_spec.rb85
-rw-r--r--spec/services/fetch_link_card_service_spec.rb10
-rw-r--r--spec/services/fetch_remote_account_service_spec.rb67
-rw-r--r--spec/services/fetch_remote_status_service_spec.rb31
-rw-r--r--spec/services/precompute_feed_service_spec.rb7
-rw-r--r--spec/services/report_service_spec.rb25
-rw-r--r--spec/services/resolve_account_service_spec.rb (renamed from spec/services/resolve_remote_account_service_spec.rb)2
-rw-r--r--spec/services/resolve_url_service_spec.rb (renamed from spec/services/fetch_remote_resource_service_spec.rb)2
-rw-r--r--spec/services/search_service_spec.rb6
-rw-r--r--spec/spec_helper.rb4
-rw-r--r--spec/views/about/show.html.haml_spec.rb3
-rw-r--r--yarn.lock131
491 files changed, 14005 insertions, 4047 deletions
diff --git a/.env.nanobox b/.env.nanobox
index 48204a6bf..0d14f8a00 100644
--- a/.env.nanobox
+++ b/.env.nanobox
@@ -13,11 +13,29 @@ 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
+
+# Optimizations
+LD_PRELOAD=/data/lib/libjemalloc.so
+
+# ImageMagick optimizations
+MAGICK_TEMPORARY_PATH=/app/tmp
+MAGICK_MEMORY_LIMIT=128MiB
+MAGICK_MAP_LIMIT=64MiB
+MAGICK_TIME_LIMIT=15
+MAGICK_AREA_LIMIT=16MP
+MAGICK_WIDTH_LIMIT=8KP
+MAGICK_HEIGHT_LIMIT=8KP
+
 # Federation
-# Note: Changing LOCAL_DOMAIN or LOCAL_HTTPS at a later time will cause unwanted side effects.
+# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
 # LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
 LOCAL_DOMAIN=${APP_NAME}.nanoapp.io
-LOCAL_HTTPS=false
+
+# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
 
 # Use this only if you need to run mastodon on a different domain than the one used for federation.
 # You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
@@ -31,7 +49,6 @@ LOCAL_HTTPS=false
 
 # Application secrets
 # Generate each with the `rake secret` task (`nanobox run bundle exec rake secret`)
-PAPERCLIP_SECRET=$PAPERCLIP_SECRET
 SECRET_KEY_BASE=$SECRET_KEY_BASE
 OTP_SECRET=$OTP_SECRET
 
@@ -131,9 +148,79 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
 
 # Cluster number setting for streaming API server.
 # If you comment out following line, cluster number will be `numOfCpuCores - 1`.
-STREAMING_CLUSTER_NUM=1
+# STREAMING_CLUSTER_NUM=1
 
 # Docker mastodon user
 # If you use Docker, you may want to assign UID/GID manually.
 # UID=1000
 # GID=1000
+
+# LDAP authentication (optional)
+# LDAP_ENABLED=true
+# LDAP_HOST=localhost
+# LDAP_PORT=389
+# LDAP_METHOD=simple_tls
+# LDAP_BASE=
+# LDAP_BIND_DN=
+# LDAP_PASSWORD=
+# LDAP_UID=cn
+
+# PAM authentication (optional)
+# PAM authentication uses for the email generation the "email" pam variable
+# and optional as fallback PAM_DEFAULT_SUFFIX
+# The pam environment variable "email" is provided by:
+# https://github.com/devkral/pam_email_extractor
+# PAM_ENABLED=true
+# Fallback Suffix for email address generation (nil by default)
+# PAM_DEFAULT_SUFFIX=pam
+# Name of the pam service (pam "auth" section is evaluated)
+# PAM_DEFAULT_SERVICE=rpam
+# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
+# PAM_CONTROLLED_SERVICE=rpam
+
+# Global OAuth settings (optional) :
+# If you have only one strategy, you may want to enable this
+# OAUTH_REDIRECT_AT_SIGN_IN=true
+
+# Optional CAS authentication (cf. omniauth-cas) :
+# CAS_ENABLED=true
+# CAS_URL=https://sso.myserver.com/
+# CAS_HOST=sso.myserver.com/
+# CAS_PORT=443
+# CAS_SSL=true
+# CAS_VALIDATE_URL=
+# CAS_CALLBACK_URL=
+# CAS_LOGOUT_URL=
+# CAS_LOGIN_URL=
+# CAS_UID_FIELD='user'
+# CAS_CA_PATH=
+# CAS_DISABLE_SSL_VERIFICATION=false
+# CAS_UID_KEY='user'
+# CAS_NAME_KEY='name'
+# CAS_EMAIL_KEY='email'
+# CAS_NICKNAME_KEY='nickname'
+# CAS_FIRST_NAME_KEY='firstname'
+# CAS_LAST_NAME_KEY='lastname'
+# CAS_LOCATION_KEY='location'
+# CAS_IMAGE_KEY='image'
+# CAS_PHONE_KEY='phone'
+
+# Optional SAML authentication (cf. omniauth-saml)
+# SAML_ENABLED=true
+# SAML_ACS_URL=
+# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback
+# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
+# SAML_IDP_CERT=
+# SAML_IDP_CERT_FINGERPRINT=
+# SAML_NAME_IDENTIFIER_FORMAT=
+# SAML_CERT=
+# SAML_PRIVATE_KEY=
+# SAML_SECURITY_WANT_ASSERTION_SIGNED=true
+# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true
+# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
+# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
+# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
+# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
+# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
+# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
+# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
diff --git a/.env.production.sample b/.env.production.sample
index cf1654e99..2af3ebb4c 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -9,11 +9,15 @@ DB_USER=postgres
 DB_NAME=postgres
 DB_PASS=
 DB_PORT=5432
+# Optional ElasticSearch configuration
+# ES_ENABLED=true
+# ES_HOST=es
+# ES_PORT=9200
 
 # Federation
 # Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
 # LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
-LOCAL_DOMAIN=example.com 
+LOCAL_DOMAIN=example.com
 
 # Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
 
@@ -29,7 +33,6 @@ LOCAL_DOMAIN=example.com
 
 # Application secrets
 # Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose)
-PAPERCLIP_SECRET=
 SECRET_KEY_BASE=
 OTP_SECRET=
 
@@ -58,7 +61,7 @@ VAPID_PUBLIC_KEY=
 # E-mail configuration
 # Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
 # If you want to use an SMTP server without authentication (e.g local Postfix relay)
-# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and 
+# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and
 # *comment* SMTP_LOGIN and SMTP_PASSWORD (leaving them blank is not enough).
 SMTP_SERVER=smtp.mailgun.org
 SMTP_PORT=587
@@ -135,6 +138,78 @@ STREAMING_CLUSTER_NUM=1
 # If you use Docker, you may want to assign UID/GID manually.
 # UID=1000
 # GID=1000
-
+ 
 # Maximum allowed character count
 # MAX_TOOT_CHARS=500
+
+# LDAP authentication (optional)
+# LDAP_ENABLED=true
+# LDAP_HOST=localhost
+# LDAP_PORT=389
+# LDAP_METHOD=simple_tls
+# LDAP_BASE=
+# LDAP_BIND_DN=
+# LDAP_PASSWORD=
+# LDAP_UID=cn
+
+# PAM authentication (optional)
+# PAM authentication uses for the email generation the "email" pam variable
+# and optional as fallback PAM_DEFAULT_SUFFIX
+# The pam environment variable "email" is provided by:
+# https://github.com/devkral/pam_email_extractor
+# PAM_ENABLED=true
+# Fallback Suffix for email address generation (nil by default)
+# PAM_DEFAULT_SUFFIX=pam
+# Name of the pam service (pam "auth" section is evaluated)
+# PAM_DEFAULT_SERVICE=rpam
+# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
+# PAM_CONTROLLED_SERVICE=rpam
+
+# Global OAuth settings (optional) :
+# If you have only one strategy, you may want to enable this
+# OAUTH_REDIRECT_AT_SIGN_IN=true
+
+# Optional CAS authentication (cf. omniauth-cas) :
+# CAS_ENABLED=true
+# CAS_URL=https://sso.myserver.com/
+# CAS_HOST=sso.myserver.com/
+# CAS_PORT=443
+# CAS_SSL=true
+# CAS_VALIDATE_URL=
+# CAS_CALLBACK_URL=
+# CAS_LOGOUT_URL=
+# CAS_LOGIN_URL=
+# CAS_UID_FIELD='user'
+# CAS_CA_PATH=
+# CAS_DISABLE_SSL_VERIFICATION=false
+# CAS_UID_KEY='user'
+# CAS_NAME_KEY='name'
+# CAS_EMAIL_KEY='email'
+# CAS_NICKNAME_KEY='nickname'
+# CAS_FIRST_NAME_KEY='firstname'
+# CAS_LAST_NAME_KEY='lastname'
+# CAS_LOCATION_KEY='location'
+# CAS_IMAGE_KEY='image'
+# CAS_PHONE_KEY='phone'
+
+# Optional SAML authentication (cf. omniauth-saml)
+# SAML_ENABLED=true
+# SAML_ACS_URL=
+# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback
+# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
+# SAML_IDP_CERT=
+# SAML_IDP_CERT_FINGERPRINT=
+# SAML_NAME_IDENTIFIER_FORMAT=
+# SAML_CERT=
+# SAML_PRIVATE_KEY=
+# SAML_SECURITY_WANT_ASSERTION_SIGNED=true
+# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true
+# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
+# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
+# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
+# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
+# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
+# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
+# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
+# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
+# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
diff --git a/.env.test b/.env.test
index e25c040ac..b57f52e30 100644
--- a/.env.test
+++ b/.env.test
@@ -1,4 +1,3 @@
 # Federation
 LOCAL_DOMAIN=cb6e6126.ngrok.io
 LOCAL_HTTPS=true
-OTP_SECRET=100c7faeef00caa29242f6b04156742bf76065771fd4117990c4282b8748ff3d99f8fdae97c982ab5bd2e6756a159121377cce4421f4a8ecd2d67bd7749a3fb4
diff --git a/.travis.yml b/.travis.yml
index 496315558..8a0931c26 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,15 +3,12 @@ cache:
   bundler: true
   yarn: true
   directories:
-  - node_modules
-  - public/assets
-  - public/packs-test
-  - tmp/cache/babel-loader
+    - node_modules
+    - public/assets
+    - public/packs-test
+    - tmp/cache/babel-loader
 dist: trusty
-sudo: required
-branches:
-  only:
-  - master
+sudo: false
 
 notifications:
   email: false
@@ -21,23 +18,21 @@ env:
     - LOCAL_DOMAIN=cb6e6126.ngrok.io
     - LOCAL_HTTPS=true
     - RAILS_ENV=test
-    - NOKOGIRI_USE_SYSTEM_LIBRARIES=true
     - PARALLEL_TEST_PROCESSORS=2
-    - "PATH=$HOME:$PATH"
 
 addons:
   postgresql: 9.4
   apt:
     sources:
-    - trusty-media
-    - sourceline: deb https://dl.yarnpkg.com/debian/ stable main
-      key_url: https://dl.yarnpkg.com/debian/pubkey.gpg
+      - trusty-media
+      - sourceline: deb https://dl.yarnpkg.com/debian/ stable main
+        key_url: https://dl.yarnpkg.com/debian/pubkey.gpg
     packages:
-    - ffmpeg
-    - libicu-dev
-    - libprotobuf-dev
-    - protobuf-compiler
-    - yarn
+      - ffmpeg
+      - libicu-dev
+      - libprotobuf-dev
+      - protobuf-compiler
+      - yarn
 
 rvm:
   - 2.4.2
@@ -47,14 +42,12 @@ services:
   - redis-server
 
 install:
-  - gem update --system
   - nvm install
   - bundle install --path=vendor/bundle --without development production --retry=3 --jobs=16
   - yarn install
 
 before_script:
   - ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile
-  - ln -s /usr/bin/x86_64-linux-gnu-g++-6 "$HOME/g++"
 
 script:
   - travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec
diff --git a/AUTHORS.md b/AUTHORS.md
new file mode 100644
index 000000000..c4bbb6014
--- /dev/null
+++ b/AUTHORS.md
@@ -0,0 +1,450 @@
+Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
+and provided thanks to the work of the following contributors:
+
+* [Gargron](https://github.com/Gargron)
+* [ykzts](https://github.com/ykzts)
+* [mjankowski](https://github.com/mjankowski)
+* [akihikodaki](https://github.com/akihikodaki)
+* [unarist](https://github.com/unarist)
+* [yiskah](https://github.com/yiskah)
+* [m4sk1n](https://github.com/m4sk1n)
+* [nolanlawson](https://github.com/nolanlawson)
+* [sorin-davidoi](https://github.com/sorin-davidoi)
+* [abcang](https://github.com/abcang)
+* [ThibG](https://github.com/ThibG)
+* [lynlynlynx](https://github.com/lynlynlynx)
+* [alpaca-tc](https://github.com/alpaca-tc)
+* [nclm](https://github.com/nclm)
+* [ineffyble](https://github.com/ineffyble)
+* [jeroenpraat](https://github.com/jeroenpraat)
+* [blackle](https://github.com/blackle)
+* [Quent-in](https://github.com/Quent-in)
+* [JantsoP](https://github.com/JantsoP)
+* [nullkal](https://github.com/nullkal)
+* [yookoala](https://github.com/yookoala)
+* [ysksn](https://github.com/ysksn)
+* [ashfurrow](https://github.com/ashfurrow)
+* [eramdam](https://github.com/eramdam)
+* [mayaeh](https://github.com/mayaeh)
+* [zunda](https://github.com/zunda)
+* [ticky](https://github.com/ticky)
+* [masarakki](https://github.com/masarakki)
+* [Wonderfall](https://github.com/Wonderfall)
+* [matteoaquila](https://github.com/matteoaquila)
+* [rkarabut](https://github.com/rkarabut)
+* [stephenburgess8](https://github.com/stephenburgess8)
+* [Kjwon15](https://github.com/Kjwon15)
+* [Artoria2e5](https://github.com/Artoria2e5)
+* [yukimochi](https://github.com/yukimochi)
+* [marrus-sh](https://github.com/marrus-sh)
+* [krainboltgreene](https://github.com/krainboltgreene)
+* [renatolond](https://github.com/renatolond)
+* [BoFFire](https://github.com/BoFFire)
+* [clworld](https://github.com/clworld)
+* [danhunsaker](https://github.com/danhunsaker)
+* [patf](https://github.com/patf)
+* [Quenty31](https://github.com/Quenty31)
+* [MitarashiDango](https://github.com/MitarashiDango)
+* [Aldarone](https://github.com/Aldarone)
+* [JeanGauthier](https://github.com/JeanGauthier)
+* [kschaper](https://github.com/kschaper)
+* [takayamaki](https://github.com/takayamaki)
+* [adbelle](https://github.com/adbelle)
+* [evanminto](https://github.com/evanminto)
+* [mabkenar](https://github.com/mabkenar)
+* [MightyPork](https://github.com/MightyPork)
+* [beatrix-bitrot](https://github.com/beatrix-bitrot)
+* [yhirano55](https://github.com/yhirano55)
+* [camponez](https://github.com/camponez)
+* [aschmitz](https://github.com/aschmitz)
+* [fpiesche](https://github.com/fpiesche)
+* [gandaro](https://github.com/gandaro)
+* [johnsudaar](https://github.com/johnsudaar)
+* [trebmuh](https://github.com/trebmuh)
+* [Sylvhem](https://github.com/Sylvhem)
+* [lindwurm](https://github.com/lindwurm)
+* [voidsatisfaction](https://github.com/voidsatisfaction)
+* [neetshin](https://github.com/neetshin)
+* [valentin2105](https://github.com/valentin2105)
+* [hikari-no-yume](https://github.com/hikari-no-yume)
+* [Angristan](https://github.com/Angristan)
+* [seefood](https://github.com/seefood)
+* [jackjennings](https://github.com/jackjennings)
+* [hcmiya](https://github.com/hcmiya)
+* [nightpool](https://github.com/nightpool)
+* [salvadorpla](https://github.com/salvadorpla)
+* [expenses](https://github.com/expenses)
+* [walf443](https://github.com/walf443)
+* [JoelQ](https://github.com/JoelQ)
+* [mistydemeo](https://github.com/mistydemeo)
+* [dunn](https://github.com/dunn)
+* [xqus](https://github.com/xqus)
+* [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
+* [fakenine](https://github.com/fakenine)
+* [tsuwatch](https://github.com/tsuwatch)
+* [victorhck](https://github.com/victorhck)
+* [puckipedia](https://github.com/puckipedia)
+* [contraexemplo](https://github.com/contraexemplo)
+* [kazu9su](https://github.com/kazu9su)
+* [Komic](https://github.com/Komic)
+* [diomed](https://github.com/diomed)
+* [rainyday](https://github.com/rainyday)
+* [kadiix](https://github.com/kadiix)
+* [kodacs](https://github.com/kodacs)
+* [ProgVal](https://github.com/ProgVal)
+* [sterdev](https://github.com/sterdev)
+* [TheKinrar](https://github.com/TheKinrar)
+* [AA4ch1](https://github.com/AA4ch1)
+* [alexgleason](https://github.com/alexgleason)
+* [cpytel](https://github.com/cpytel)
+* [northerner](https://github.com/northerner)
+* [hnrysmth](https://github.com/hnrysmth)
+* [hugogameiro](https://github.com/hugogameiro)
+* [JohnD28](https://github.com/JohnD28)
+* [znz](https://github.com/znz)
+* [Naouak](https://github.com/Naouak)
+* [rtucker](https://github.com/rtucker)
+* [reneklacan](https://github.com/reneklacan)
+* [KScl](https://github.com/KScl)
+* [SerCom-KC](https://github.com/SerCom-KC)
+* [tcitworld](https://github.com/tcitworld)
+* [geta6](https://github.com/geta6)
+* [goofy-bz](https://github.com/goofy-bz)
+* [happycoloredbanana](https://github.com/happycoloredbanana)
+* [leopku](https://github.com/leopku)
+* [SansPseudoFix](https://github.com/SansPseudoFix)
+* [tomfhowe](https://github.com/tomfhowe)
+* [noraworld](https://github.com/noraworld)
+* [fvh-P](https://github.com/fvh-P)
+* [178inaba](https://github.com/178inaba)
+* [devkral](https://github.com/devkral)
+* [alyssais](https://github.com/alyssais)
+* [kodnaplakal](https://github.com/kodnaplakal)
+* [stalker314314](https://github.com/stalker314314)
+* [huertanix](https://github.com/huertanix)
+* [genesixx](https://github.com/genesixx)
+* [fhemberger](https://github.com/fhemberger)
+* [halkeye](https://github.com/halkeye)
+* [treby](https://github.com/treby)
+* [d6rkaiz](https://github.com/d6rkaiz)
+* [jpdevries](https://github.com/jpdevries)
+* [rndm-stranger](https://github.com/rndm-stranger)
+* [saper](https://github.com/saper)
+* [nevillepark](https://github.com/nevillepark)
+* [ornithocoder](https://github.com/ornithocoder)
+* [pierreozoux](https://github.com/pierreozoux)
+* [ramlmn](https://github.com/ramlmn)
+* [harukasan](https://github.com/harukasan)
+* [stamak](https://github.com/stamak)
+* [Eychics](https://github.com/Eychics)
+* [thor-the-norseman](https://github.com/thor-the-norseman)
+* [0x70b1a5](https://github.com/0x70b1a5)
+* [gled-rs](https://github.com/gled-rs)
+* [R0ckweb](https://github.com/R0ckweb)
+* [esetomo](https://github.com/esetomo)
+* [foxiehkins](https://github.com/foxiehkins)
+* [sdukhovni](https://github.com/sdukhovni)
+* [unsmell](https://github.com/unsmell)
+* [chriswmartin](https://github.com/chriswmartin)
+* [vahnj](https://github.com/vahnj)
+* [ikuradon](https://github.com/ikuradon)
+* [AndreLewin](https://github.com/AndreLewin)
+* [redtachyons](https://github.com/redtachyons)
+* [thurloat](https://github.com/thurloat)
+* [aaribaud](https://github.com/aaribaud)
+* [estuans](https://github.com/estuans)
+* [dissolve](https://github.com/dissolve)
+* [PurpleBooth](https://github.com/PurpleBooth)
+* [bradurani](https://github.com/bradurani)
+* [wavebeem](https://github.com/wavebeem)
+* [bruwalfas](https://github.com/bruwalfas)
+* [foxsan48](https://github.com/foxsan48)
+* [wchristian](https://github.com/wchristian)
+* [muffinista](https://github.com/muffinista)
+* [cdutson](https://github.com/cdutson)
+* [farlistener](https://github.com/farlistener)
+* [DavidLibeau](https://github.com/DavidLibeau)
+* [SirCmpwn](https://github.com/SirCmpwn)
+* [MasterGroosha](https://github.com/MasterGroosha)
+* [Fjoerfoks](https://github.com/Fjoerfoks)
+* [fmauNeko](https://github.com/fmauNeko)
+* [gloaec](https://github.com/gloaec)
+* [greysteil](https://github.com/greysteil)
+* [unstabler](https://github.com/unstabler)
+* [potato4d](https://github.com/potato4d)
+* [h-izumi](https://github.com/h-izumi)
+* [ErikXXon](https://github.com/ErikXXon)
+* [ian-kelling](https://github.com/ian-kelling)
+* [foozmeat](https://github.com/foozmeat)
+* [jasonrhodes](https://github.com/jasonrhodes)
+* [asm](https://github.com/asm)
+* [jviide](https://github.com/jviide)
+* [crakaC](https://github.com/crakaC)
+* [tkbky](https://github.com/tkbky)
+* [Kazhnuz](https://github.com/Kazhnuz)
+* [alimony](https://github.com/alimony)
+* [mig5](https://github.com/mig5)
+* [ndarville](https://github.com/ndarville)
+* [Abzol](https://github.com/Abzol)
+* [xPaw](https://github.com/xPaw)
+* [raymestalez](https://github.com/raymestalez)
+* [sim6](https://github.com/sim6)
+* [ekiru](https://github.com/ekiru)
+* [Technowix](https://github.com/Technowix)
+* [ThomasLeister](https://github.com/ThomasLeister)
+* [mcat-ee](https://github.com/mcat-ee)
+* [tototoshi](https://github.com/tototoshi)
+* [VirtuBox](https://github.com/VirtuBox)
+* [kaniini](https://github.com/kaniini)
+* [vayan](https://github.com/vayan)
+* [yannicka](https://github.com/yannicka)
+* [ikasoumen](https://github.com/ikasoumen)
+* [zacanger](https://github.com/zacanger)
+* [amazedkoumei](https://github.com/amazedkoumei)
+* [anon5r](https://github.com/anon5r)
+* [codl](https://github.com/codl)
+* [barzamin](https://github.com/barzamin)
+* [fhalna](https://github.com/fhalna)
+* [haoyayoi](https://github.com/haoyayoi)
+* [ik11235](https://github.com/ik11235)
+* [kawax](https://github.com/kawax)
+* [007lva](https://github.com/007lva)
+* [matsurai25](https://github.com/matsurai25)
+* [mecab](https://github.com/mecab)
+* [nicobz25](https://github.com/nicobz25)
+* [oliverkeeble](https://github.com/oliverkeeble)
+* [pinfort](https://github.com/pinfort)
+* [rbaumert](https://github.com/rbaumert)
+* [usagi-f](https://github.com/usagi-f)
+* [vidarlee](https://github.com/vidarlee)
+* [vjackson725](https://github.com/vjackson725)
+* [wxcafe](https://github.com/wxcafe)
+* [rinsuki](https://github.com/rinsuki)
+* [cygnan](https://github.com/cygnan)
+* [Awea](https://github.com/Awea)
+* [halcy](https://github.com/halcy)
+* [bounshi](https://github.com/bounshi)
+* [8398a7](https://github.com/8398a7)
+* [857b](https://github.com/857b)
+* [unascribed](https://github.com/unascribed)
+* [Aguay-val](https://github.com/Aguay-val)
+* [knu](https://github.com/knu)
+* [alxrcs](https://github.com/alxrcs)
+* [console-cowboy](https://github.com/console-cowboy)
+* [pointlessone](https://github.com/pointlessone)
+* [a2](https://github.com/a2)
+* [0xa](https://github.com/0xa)
+* [virtualpain](https://github.com/virtualpain)
+* [sapphirus](https://github.com/sapphirus)
+* [amandavisconti](https://github.com/amandavisconti)
+* [ameliavoncat](https://github.com/ameliavoncat)
+* [ilpianista](https://github.com/ilpianista)
+* [andydrop](https://github.com/andydrop)
+* [schas002](https://github.com/schas002)
+* [jumbosushi](https://github.com/jumbosushi)
+* [ayumin](https://github.com/ayumin)
+* [BaptisteGelez](https://github.com/BaptisteGelez)
+* [bzg](https://github.com/bzg)
+* [benediktg](https://github.com/benediktg)
+* [blakebarnett](https://github.com/blakebarnett)
+* [bradj](https://github.com/bradj)
+* [brycied00d](https://github.com/brycied00d)
+* [carlosjs23](https://github.com/carlosjs23)
+* [cgxxx](https://github.com/cgxxx)
+* [chrisheninger](https://github.com/chrisheninger)
+* [chris-martin](https://github.com/chris-martin)
+* [DoubleMalt](https://github.com/DoubleMalt)
+* [Moosh-be](https://github.com/Moosh-be)
+* [Motoma](https://github.com/Motoma)
+* [chriswk](https://github.com/chriswk)
+* [csu](https://github.com/csu)
+* [kklleemm](https://github.com/kklleemm)
+* [monsterpit-daggertooth](https://github.com/monsterpit-daggertooth)
+* [watilde](https://github.com/watilde)
+* [daprice](https://github.com/daprice)
+* [dar5hak](https://github.com/dar5hak)
+* [kant](https://github.com/kant)
+* [singingwolfboy](https://github.com/singingwolfboy)
+* [davidcelis](https://github.com/davidcelis)
+* [yipdw](https://github.com/yipdw)
+* [debanshuk](https://github.com/debanshuk)
+* [dblandin](https://github.com/dblandin)
+* [aranaur](https://github.com/aranaur)
+* [d3vgru](https://github.com/d3vgru)
+* [Elizafox](https://github.com/Elizafox)
+* [ericblade](https://github.com/ericblade)
+* [mikoim](https://github.com/mikoim)
+* [siuying](https://github.com/siuying)
+* [hattori6789](https://github.com/hattori6789)
+* [algernon](https://github.com/algernon)
+* [Fastbyte01](https://github.com/Fastbyte01)
+* [myfreeweb](https://github.com/myfreeweb)
+* [gfaivre](https://github.com/gfaivre)
+* [Fiaxhs](https://github.com/Fiaxhs)
+* [reedcourty](https://github.com/reedcourty)
+* [anneau](https://github.com/anneau)
+* [HellPie](https://github.com/HellPie)
+* [Habu-Kagumba](https://github.com/Habu-Kagumba)
+* [hinaloe](https://github.com/hinaloe)
+* [suzukaze](https://github.com/suzukaze)
+* [Hiromi-Kai](https://github.com/Hiromi-Kai)
+* [musashino205](https://github.com/musashino205)
+* [iwaim](https://github.com/iwaim)
+* [valrus](https://github.com/valrus)
+* [IMcD23](https://github.com/IMcD23)
+* [yi0713](https://github.com/yi0713)
+* [immae](https://github.com/immae)
+* [iblech](https://github.com/iblech)
+* [jack-michaud](https://github.com/jack-michaud)
+* [Floppy](https://github.com/Floppy)
+* [loomchild](https://github.com/loomchild)
+* [docjkl](https://github.com/docjkl)
+* [TrollDecker](https://github.com/TrollDecker)
+* [jmontane](https://github.com/jmontane)
+* [jonathanklee](https://github.com/jonathanklee)
+* [jguerder](https://github.com/jguerder)
+* [Jehops](https://github.com/Jehops)
+* [joshuap](https://github.com/joshuap)
+* [Tiwy57](https://github.com/Tiwy57)
+* [xuv](https://github.com/xuv)
+* [Jnsll](https://github.com/Jnsll)
+* [j0k3r](https://github.com/j0k3r)
+* [KEINOS](https://github.com/KEINOS)
+* [futoase](https://github.com/futoase)
+* [abjectio](https://github.com/abjectio)
+* [mkody](https://github.com/mkody)
+* [connyduck](https://github.com/connyduck)
+* [k0ta0uchi](https://github.com/k0ta0uchi)
+* [KrzysiekJ](https://github.com/KrzysiekJ)
+* [leowzukw](https://github.com/leowzukw)
+* [lmorchard](https://github.com/lmorchard)
+* [cacheflow](https://github.com/cacheflow)
+* [ldidry](https://github.com/ldidry)
+* [jemus42](https://github.com/jemus42)
+* [lfuelling](https://github.com/lfuelling)
+* [Grabacr07](https://github.com/Grabacr07)
+* [mistermantas](https://github.com/mistermantas)
+* [wirehack7](https://github.com/wirehack7)
+* [marvinkopf](https://github.com/marvinkopf)
+* [otsune](https://github.com/otsune)
+* [m-blc](https://github.com/m-blc)
+* [matt-auckland](https://github.com/matt-auckland)
+* [mattjmattj](https://github.com/mattjmattj)
+* [mtparet](https://github.com/mtparet)
+* [maximeborges](https://github.com/maximeborges)
+* [minacle](https://github.com/minacle)
+* [michaeljdeeb](https://github.com/michaeljdeeb)
+* [Themimitoof](https://github.com/Themimitoof)
+* [cyweo](https://github.com/cyweo)
+* [M1dgard](https://github.com/M1dgard)
+* [mike-burns](https://github.com/mike-burns)
+* [verymilan](https://github.com/verymilan)
+* [milmazz](https://github.com/milmazz)
+* [Mnkai](https://github.com/Mnkai)
+* [mitchhentges](https://github.com/mitchhentges)
+* [moritzheiber](https://github.com/moritzheiber)
+* [mouse-reeve](https://github.com/mouse-reeve)
+* [lae](https://github.com/lae)
+* [Nanamachi](https://github.com/Nanamachi)
+* [ngerakines](https://github.com/ngerakines)
+* [vonneudeck](https://github.com/vonneudeck)
+* [Ninetailed](https://github.com/Ninetailed)
+* [k24](https://github.com/k24)
+* [noiob](https://github.com/noiob)
+* [kwaio](https://github.com/kwaio)
+* [norayr](https://github.com/norayr)
+* [joyeusenoelle](https://github.com/joyeusenoelle)
+* [OlivierNicole](https://github.com/OlivierNicole)
+* [Otakan951](https://github.com/Otakan951)
+* [fahy](https://github.com/fahy)
+* [Pangoraw](https://github.com/Pangoraw)
+* [pwoolcoc](https://github.com/pwoolcoc)
+* [peterkeen](https://github.com/peterkeen)
+* [petzah](https://github.com/petzah)
+* [ignisf](https://github.com/ignisf)
+* [rfwatson](https://github.com/rfwatson)
+* [rfreebern](https://github.com/rfreebern)
+* [sylph01](https://github.com/sylph01)
+* [staticsafe](https://github.com/staticsafe)
+* [snwh](https://github.com/snwh)
+* [skoji](https://github.com/skoji)
+* [ScienJus](https://github.com/ScienJus)
+* [larkinscott](https://github.com/larkinscott)
+* [imolein](https://github.com/imolein)
+* [blinry](https://github.com/blinry)
+* [Noiwex](https://github.com/Noiwex)
+* [yuki764](https://github.com/yuki764)
+* [shnjp](https://github.com/shnjp)
+* [ernix](https://github.com/ernix)
+* [rosylilly](https://github.com/rosylilly)
+* [shouko](https://github.com/shouko)
+* [sossii](https://github.com/sossii)
+* [StefOfficiel](https://github.com/StefOfficiel)
+* [svetlik](https://github.com/svetlik)
+* [dereckson](https://github.com/dereckson)
+* [theboss](https://github.com/theboss)
+* [takp](https://github.com/takp)
+* [tkusano](https://github.com/tkusano)
+* [TheInventrix](https://github.com/TheInventrix)
+* [shug0](https://github.com/shug0)
+* [Fortyseven](https://github.com/Fortyseven)
+* [tobypinder](https://github.com/tobypinder)
+* [tomosm](https://github.com/tomosm)
+* [TomoyaShibata](https://github.com/TomoyaShibata)
+* [TrashMacNugget](https://github.com/TrashMacNugget)
+* [treyssatvincent](https://github.com/treyssatvincent)
+* [optikfluffel](https://github.com/optikfluffel)
+* [vmincev](https://github.com/vmincev)
+* [waldyrious](https://github.com/waldyrious)
+* [tahnok](https://github.com/tahnok)
+* [YDrogen](https://github.com/YDrogen)
+* [YOSHIOKAEiichiro](https://github.com/YOSHIOKAEiichiro)
+* [S-YOU](https://github.com/S-YOU)
+* [YaQ00](https://github.com/YaQ00)
+* [yanakend](https://github.com/yanakend)
+* [orzFly](https://github.com/orzFly)
+* [chansuke](https://github.com/chansuke)
+* [yuntan](https://github.com/yuntan)
+* [LogicalDash](https://github.com/LogicalDash)
+* [ZiiX](https://github.com/ZiiX)
+* [benklop](https://github.com/benklop)
+* [caasi](https://github.com/caasi)
+* [caesarologia](https://github.com/caesarologia)
+* [chrolis](https://github.com/chrolis)
+* [cormojs](https://github.com/cormojs)
+* [cpsdqs](https://github.com/cpsdqs)
+* [d0p1s4m4](https://github.com/d0p1s4m4)
+* [evilny0](https://github.com/evilny0)
+* [febrezo](https://github.com/febrezo)
+* [fsubal](https://github.com/fsubal)
+* [dikky1218](https://github.com/dikky1218)
+* [gentarok](https://github.com/gentarok)
+* [hakoai](https://github.com/hakoai)
+* [chaosbunker](https://github.com/chaosbunker)
+* [isati](https://github.com/isati)
+* [jkap](https://github.com/jkap)
+* [jirayudech](https://github.com/jirayudech)
+* [jukper](https://github.com/jukper)
+* [karlyeurl](https://github.com/karlyeurl)
+* [kedamaDQ](https://github.com/kedamaDQ)
+* [kuro5hin](https://github.com/kuro5hin)
+* [maxypy](https://github.com/maxypy)
+* [marcus-herrmann](https://github.com/marcus-herrmann)
+* [mshrtkch](https://github.com/mshrtkch)
+* [muan](https://github.com/muan)
+* [rch850](https://github.com/rch850)
+* [roikale](https://github.com/roikale)
+* [rysiekpl](https://github.com/rysiekpl)
+* [saturday06](https://github.com/saturday06)
+* [scriptjunkie](https://github.com/scriptjunkie)
+* [seekr](https://github.com/seekr)
+* [syui](https://github.com/syui)
+* [tackeyy](https://github.com/tackeyy)
+* [tmyt](https://github.com/tmyt)
+* [utam0k](https://github.com/utam0k)
+* [vpzomtrrfrt](https://github.com/vpzomtrrfrt)
+* [walfie](https://github.com/walfie)
+* [y-temp4](https://github.com/y-temp4)
+* [ymmtmdk](https://github.com/ymmtmdk)
+
+This document is provided for informational purposes only. Since it is only updated once per release, the version you are looking at may be currently out of date. To see the full list of contributors, consider looking at the [git history](https://github.com/tootsuite/mastodon/graphs/contributors) instead.
diff --git a/Dockerfile b/Dockerfile
index 6d8465ddc..02c32359e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,14 @@
 FROM ruby:2.5.0-alpine3.7
 
 LABEL maintainer="https://github.com/tootsuite/mastodon" \
-      description="A GNU Social-compatible microblogging server"
+      description="Your self-hosted, globally interconnected microblogging community"
 
-ENV UID=991 GID=991 \
-    RAILS_SERVE_STATIC_FILES=true \
+ARG UID=991
+ARG GID=991
+
+ENV RAILS_SERVE_STATIC_FILES=true \
     RAILS_ENV=production NODE_ENV=production
 
-ARG YARN_VERSION=1.3.2
-ARG YARN_DOWNLOAD_SHA256=6cfe82e530ef0837212f13e45c1565ba53f5199eec2527b85ecbcd88bf26821d
 ARG LIBICONV_VERSION=1.15
 ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
 
@@ -30,25 +30,17 @@ RUN apk -U upgrade \
     ca-certificates \
     ffmpeg \
     file \
-    git \
     icu-libs \
     imagemagick \
     libidn \
     libpq \
     nodejs \
-    nodejs-npm \
     protobuf \
-    su-exec \
     tini \
     tzdata \
+    yarn \
  && update-ca-certificates \
- && mkdir -p /tmp/src /opt \
- && wget -O yarn.tar.gz "https://github.com/yarnpkg/yarn/releases/download/v$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \
- && echo "$YARN_DOWNLOAD_SHA256 *yarn.tar.gz" | sha256sum -c - \
- && tar -xzf yarn.tar.gz -C /tmp/src \
- && rm yarn.tar.gz \
- && mv /tmp/src/yarn-v$YARN_VERSION /opt/yarn \
- && ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
+ && mkdir -p /tmp/src \
  && wget -O libiconv.tar.gz "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
  && echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \
  && tar -xzf libiconv.tar.gz -C /tmp/src \
@@ -71,12 +63,16 @@ RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-in
  && yarn --pure-lockfile \
  && yarn cache clean
 
-COPY . /mastodon
+RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon \
+ && mkdir -p /mastodon/public/system /mastodon/public/assets /mastodon/public/packs \
+ && chown -R mastodon:mastodon /mastodon/public
 
-COPY docker_entrypoint.sh /usr/local/bin/run
+COPY . /mastodon
 
-RUN chmod +x /usr/local/bin/run
+RUN chown -R mastodon:mastodon /mastodon
 
 VOLUME /mastodon/public/system /mastodon/public/assets /mastodon/public/packs
 
-ENTRYPOINT ["/usr/local/bin/run"]
+USER mastodon
+
+ENTRYPOINT ["/sbin/tini", "--"]
diff --git a/Gemfile b/Gemfile
index dc72851b3..22192db4d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,7 +7,6 @@ gem 'pkg-config', '~> 1.2'
 
 gem 'puma', '~> 3.10'
 gem 'rails', '~> 5.1.4'
-gem 'uglifier', '~> 3.2'
 
 gem 'hamlit-rails', '~> 0.2'
 gem 'pg', '~> 0.20'
@@ -21,6 +20,7 @@ gem 'fog-openstack', '~> 0.1', require: false
 gem 'paperclip', '~> 5.1'
 gem 'paperclip-av-transcoder', '~> 0.6'
 gem 'posix-spawn'
+gem 'streamio-ffmpeg', '~> 3.0'
 
 gem 'active_model_serializers', '~> 0.10'
 gem 'addressable', '~> 2.5'
@@ -28,16 +28,26 @@ gem 'bootsnap'
 gem 'browser'
 gem 'charlock_holmes', '~> 0.7.5'
 gem 'iso-639'
+gem 'chewy', '~> 5.0'
 gem 'cld3', '~> 3.2.0'
 gem 'devise', '~> 4.4'
 gem 'devise-two-factor', '~> 3.0'
+
+gem 'devise_pam_authenticatable2', '~> 8.0', install_if: -> { ENV['PAM_ENABLED'] == 'true' }
+gem 'net-ldap', '~> 0.10'
+gem 'omniauth-cas', '~> 1.1'
+gem 'omniauth-saml', '~> 1.10'
+gem 'omniauth', '~> 1.2'
+
 gem 'doorkeeper', '~> 4.2'
 gem 'fast_blank', '~> 1.0'
-gem 'goldfinger', '~> 2.0'
+gem 'fastimage'
+gem 'goldfinger', '~> 2.1'
 gem 'hiredis', '~> 0.6'
 gem 'redis-namespace', '~> 1.5'
+gem 'html2text'
 gem 'htmlentities', '~> 4.3'
-gem 'http', '~> 2.2'
+gem 'http', '~> 3.0'
 gem 'http_accept_language', '~> 2.1'
 gem 'httplog', '~> 0.99'
 gem 'idn-ruby', require: 'idn'
@@ -70,6 +80,8 @@ gem 'simple-navigation', '~> 4.0'
 gem 'simple_form', '~> 3.4'
 gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
 gem 'strong_migrations'
+gem 'tty-command'
+gem 'tty-prompt'
 gem 'twitter-text', '~> 1.14'
 gem 'tzinfo-data', '~> 1.2017'
 gem 'webpacker', '~> 3.0'
@@ -86,6 +98,10 @@ group :development, :test do
   gem 'rspec-rails', '~> 3.7'
 end
 
+group :production, :test do
+  gem 'private_address_check', '~> 0.4.1'
+end
+
 group :test do
   gem 'capybara', '~> 2.15'
   gem 'climate_control', '~> 0.2'
@@ -106,6 +122,7 @@ group :development do
   gem 'bullet', '~> 5.5'
   gem 'letter_opener', '~> 1.4'
   gem 'letter_opener_web', '~> 1.3'
+  gem 'memory_profiler'
   gem 'rubocop', require: false
   gem 'brakeman', '~> 4.0', require: false
   gem 'bundler-audit', '~> 0.6', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 87298fc77..693773d36 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -70,7 +70,7 @@ GEM
       coderay (>= 1.0.0)
       erubi (>= 1.0.0)
       rack (>= 0.9.0)
-    binding_of_caller (0.7.3)
+    binding_of_caller (0.8.0)
       debug_inspector (>= 0.0.1)
     bootsnap (1.1.5)
       msgpack (~> 1.0)
@@ -109,6 +109,10 @@ GEM
     case_transform (0.2)
       activesupport
     charlock_holmes (0.7.5)
+    chewy (5.0.0)
+      activesupport (>= 4.0)
+      elasticsearch (>= 2.0.0)
+      elasticsearch-dsl
     chunky_png (1.3.8)
     cld3 (3.2.2)
       ffi (>= 1.1.0, < 1.10.0)
@@ -137,6 +141,9 @@ GEM
       devise (~> 4.0)
       railties (< 5.2)
       rotp (~> 2.0)
+    devise_pam_authenticatable2 (8.0.1)
+      devise (>= 4.0.0)
+      rpam2 (~> 3.0)
     diff-lcs (1.3)
     docile (1.1.5)
     domain_name (0.5.20170404)
@@ -151,16 +158,28 @@ GEM
       json
       thread
       thread_safe
+    elasticsearch (6.0.1)
+      elasticsearch-api (= 6.0.1)
+      elasticsearch-transport (= 6.0.1)
+    elasticsearch-api (6.0.1)
+      multi_json
+    elasticsearch-dsl (0.1.5)
+    elasticsearch-transport (6.0.1)
+      faraday
+      multi_json
     encryptor (3.0.0)
+    equatable (0.5.0)
     erubi (1.7.0)
     et-orbi (1.0.8)
       tzinfo
     excon (0.59.0)
-    execjs (2.7.0)
     fabrication (2.18.0)
     faker (1.8.4)
       i18n (~> 0.5)
+    faraday (0.14.0)
+      multipart-post (>= 1.2, < 3)
     fast_blank (1.0.0)
+    fastimage (2.1.1)
     ffi (1.9.18)
     fog-core (1.45.0)
       builder
@@ -181,9 +200,9 @@ GEM
       ruby-progressbar (~> 1.4)
     globalid (0.4.1)
       activesupport (>= 4.2.0)
-    goldfinger (2.0.1)
+    goldfinger (2.1.0)
       addressable (~> 2.5)
-      http (~> 2.2)
+      http (~> 3.0)
       nokogiri (~> 1.8)
       oj (~> 3.0)
     hamlit (2.8.5)
@@ -198,24 +217,28 @@ GEM
     hamster (3.0.0)
       concurrent-ruby (~> 1.0)
     hashdiff (0.3.7)
+    hashie (3.5.7)
     highline (1.7.10)
     hiredis (0.6.1)
+    hitimes (1.2.6)
     hkdf (0.3.0)
+    html2text (0.2.1)
+      nokogiri (~> 1.6)
     htmlentities (4.3.4)
-    http (2.2.2)
+    http (3.0.0)
       addressable (~> 2.3)
       http-cookie (~> 1.0)
-      http-form_data (~> 1.0.1)
+      http-form_data (>= 2.0.0.pre.pre2, < 3)
       http_parser.rb (~> 0.6.0)
     http-cookie (1.0.3)
       domain_name (~> 0.5)
-    http-form_data (1.0.3)
+    http-form_data (2.0.0)
     http_accept_language (2.1.1)
     http_parser.rb (0.6.0)
     httplog (0.99.7)
       colorize
       rack
-    i18n (0.9.1)
+    i18n (0.9.3)
       concurrent-ruby (~> 1.0)
     i18n-tasks (0.9.19)
       activesupport (>= 4.0.2)
@@ -274,6 +297,7 @@ GEM
       mini_mime (>= 0.1.1)
     mario-redis-lock (1.2.0)
       redis (~> 3, >= 3.0.5)
+    memory_profiler (0.9.10)
     method_source (0.9.0)
     microformats (4.0.7)
       json
@@ -284,9 +308,12 @@ GEM
     mimemagic (0.3.2)
     mini_mime (1.0.0)
     mini_portile2 (2.3.0)
-    minitest (5.10.3)
+    minitest (5.11.3)
     msgpack (1.1.0)
     multi_json (1.12.2)
+    multipart-post (2.0.0)
+    necromancer (0.4.0)
+    net-ldap (0.16.1)
     net-scp (1.2.1)
       net-ssh (>= 2.6.5)
     net-ssh (4.2.0)
@@ -300,14 +327,24 @@ GEM
       concurrent-ruby (~> 1.0.0)
       sidekiq (>= 3.5.0)
       statsd-ruby (~> 1.2.0)
-    oj (3.3.9)
+    oj (3.3.10)
+    omniauth (1.8.1)
+      hashie (>= 3.4.6, < 3.6.0)
+      rack (>= 1.6.2, < 3)
+    omniauth-cas (1.1.1)
+      addressable (~> 2.3)
+      nokogiri (~> 1.5)
+      omniauth (~> 1.2)
+    omniauth-saml (1.10.0)
+      omniauth (~> 1.3, >= 1.3.2)
+      ruby-saml (~> 1.7)
     orm_adapter (0.5.0)
-    ostatus2 (2.0.2)
-      addressable (~> 2.4)
-      http (~> 2.0)
-      nokogiri (~> 1.6)
+    ostatus2 (2.0.3)
+      addressable (~> 2.5)
+      http (~> 3.0)
+      nokogiri (~> 1.8)
     ox (2.8.2)
-    paperclip (5.1.0)
+    paperclip (5.2.1)
       activemodel (>= 4.2.0)
       activesupport (>= 4.2.0)
       cocaine (~> 0.5.5)
@@ -321,6 +358,9 @@ GEM
       parallel
     parser (2.4.0.2)
       ast (~> 2.3)
+    pastel (0.7.2)
+      equatable (~> 0.5.0)
+      tty-color (~> 0.4.0)
     pg (0.21.0)
     pghero (1.7.0)
       activerecord
@@ -334,6 +374,7 @@ GEM
     premailer-rails (1.10.1)
       actionmailer (>= 3, < 6)
       premailer (~> 1.7, >= 1.7.9)
+    private_address_check (0.4.1)
     pry (0.11.3)
       coderay (~> 1.1.0)
       method_source (~> 0.9.0)
@@ -421,6 +462,7 @@ GEM
       actionpack (>= 4.2.0, < 5.3)
       railties (>= 4.2.0, < 5.3)
     rotp (2.1.2)
+    rpam2 (3.1.0)
     rqrcode (0.10.1)
       chunky_png (~> 1.0)
     rspec-core (3.7.0)
@@ -452,6 +494,8 @@ GEM
       unicode-display_width (~> 1.0, >= 1.0.1)
     ruby-oembed (0.12.0)
     ruby-progressbar (1.9.0)
+    ruby-saml (1.7.2)
+      nokogiri (>= 1.5.10)
     rufus-scheduler (3.4.2)
       et-orbi (~> 1.0)
     safe_yaml (1.0.4)
@@ -504,6 +548,8 @@ GEM
       net-scp (>= 1.1.2)
       net-ssh (>= 2.8.0)
     statsd-ruby (1.2.1)
+    streamio-ffmpeg (3.0.2)
+      multi_json (~> 1.8)
     strong_migrations (0.1.9)
       activerecord (>= 3.2.0)
     temple (0.8.0)
@@ -513,14 +559,29 @@ GEM
     thread (0.2.2)
     thread_safe (0.3.6)
     tilt (2.0.8)
+    timers (4.1.2)
+      hitimes
+    tty-color (0.4.2)
+    tty-command (0.7.0)
+      pastel (~> 0.7.0)
+    tty-cursor (0.5.0)
+    tty-prompt (0.15.0)
+      necromancer (~> 0.4.0)
+      pastel (~> 0.7.0)
+      timers (~> 4.0)
+      tty-cursor (~> 0.5.0)
+      tty-reader (~> 0.2.0)
+    tty-reader (0.2.0)
+      tty-cursor (~> 0.5.0)
+      tty-screen (~> 0.6.4)
+      wisper (~> 2.0.0)
+    tty-screen (0.6.4)
     twitter-text (1.14.7)
       unf (~> 0.1.0)
     tzinfo (1.2.4)
       thread_safe (~> 0.1)
     tzinfo-data (1.2017.3)
       tzinfo (>= 1.0.0)
-    uglifier (3.2.0)
-      execjs (>= 0.3.0, < 3)
     unf (0.1.4)
       unf_ext
     unf_ext (0.0.7.4)
@@ -542,6 +603,7 @@ GEM
     websocket-driver (0.6.5)
       websocket-extensions (>= 0.1.0)
     websocket-extensions (0.1.3)
+    wisper (2.0.0)
     xpath (2.1.0)
       nokogiri (~> 1.3)
 
@@ -567,24 +629,28 @@ DEPENDENCIES
   capistrano-yarn (~> 2.0)
   capybara (~> 2.15)
   charlock_holmes (~> 0.7.5)
+  chewy (~> 5.0)
   cld3 (~> 3.2.0)
   climate_control (~> 0.2)
   devise (~> 4.4)
   devise-two-factor (~> 3.0)
+  devise_pam_authenticatable2 (~> 8.0)
   doorkeeper (~> 4.2)
   dotenv-rails (~> 2.2)
   fabrication (~> 2.18)
   faker (~> 1.7)
   fast_blank (~> 1.0)
+  fastimage
   fog-core (~> 1.45)
   fog-local (~> 0.4)
   fog-openstack (~> 0.1)
   fuubar (~> 2.2)
-  goldfinger (~> 2.0)
+  goldfinger (~> 2.1)
   hamlit-rails (~> 0.2)
   hiredis (~> 0.6)
+  html2text
   htmlentities (~> 4.3)
-  http (~> 2.2)
+  http (~> 3.0)
   http_accept_language (~> 2.1)
   httplog (~> 0.99)
   i18n-tasks (~> 0.9)
@@ -597,11 +663,16 @@ DEPENDENCIES
   link_header (~> 0.0)
   lograge (~> 0.7)
   mario-redis-lock (~> 1.2)
+  memory_profiler
   microformats (~> 4.0)
   mime-types (~> 3.1)
+  net-ldap (~> 0.10)
   nokogiri (~> 1.8)
   nsa (~> 0.2)
   oj (~> 3.3)
+  omniauth (~> 1.2)
+  omniauth-cas (~> 1.1)
+  omniauth-saml (~> 1.10)
   ostatus2 (~> 2.0)
   ox (~> 2.8)
   paperclip (~> 5.1)
@@ -612,6 +683,7 @@ DEPENDENCIES
   pkg-config (~> 1.2)
   posix-spawn
   premailer-rails
+  private_address_check (~> 0.4.1)
   pry-rails (~> 0.3)
   puma (~> 3.10)
   pundit (~> 1.1)
@@ -642,10 +714,12 @@ DEPENDENCIES
   simple_form (~> 3.4)
   simplecov (~> 0.14)
   sprockets-rails (~> 3.2)
+  streamio-ffmpeg (~> 3.0)
   strong_migrations
+  tty-command
+  tty-prompt
   twitter-text (~> 1.14)
   tzinfo-data (~> 1.2017)
-  uglifier (~> 3.2)
   webmock (~> 3.0)
   webpacker (~> 3.0)
   webpush
diff --git a/Vagrantfile b/Vagrantfile
index 351ab5cfa..e52ec556b 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -39,6 +39,7 @@ sudo apt-get install \
   libidn11-dev \
   libprotobuf-dev \
   libreadline-dev \
+  libpam0g-dev \
   -y
 
 # Install rvm
@@ -48,7 +49,7 @@ curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-instal
 source /home/vagrant/.rvm/scripts/rvm
 
 # Install Ruby
-rvm install ruby-$RUBY_VERSION
+rvm reinstall ruby-$RUBY_VERSION --disable-binary
 
 # Configure database
 sudo -u postgres createuser -U postgres vagrant -s
@@ -79,7 +80,7 @@ VAGRANTFILE_API_VERSION = "2"
 
 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
 
-  config.vm.box = "ubuntu/trusty64"
+  config.vm.box = "ubuntu/xenial64"
 
   config.vm.provider :virtualbox do |vb|
     vb.name = "mastodon"
diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb
new file mode 100644
index 000000000..8bf5b4af7
--- /dev/null
+++ b/app/chewy/statuses_index.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+class StatusesIndex < Chewy::Index
+  settings index: { refresh_interval: '15m' }, analysis: {
+    filter: {
+      english_stop: {
+        type: 'stop',
+        stopwords: '_english_',
+      },
+      english_stemmer: {
+        type: 'stemmer',
+        language: 'english',
+      },
+      english_possessive_stemmer: {
+        type: 'stemmer',
+        language: 'possessive_english',
+      },
+    },
+    analyzer: {
+      content: {
+        tokenizer: 'uax_url_email',
+        filter: %w(
+          english_possessive_stemmer
+          lowercase
+          asciifolding
+          cjk_width
+          english_stop
+          english_stemmer
+        ),
+      },
+    },
+  }
+
+  define_type ::Status.without_reblogs do
+    crutch :mentions do |collection|
+      data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
+      data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
+    end
+
+    crutch :favourites do |collection|
+      data = ::Favourite.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
+      data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
+    end
+
+    crutch :reblogs do |collection|
+      data = ::Status.where(reblog_of_id: collection.map(&:id)).pluck(:reblog_of_id, :account_id)
+      data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
+    end
+
+    root date_detection: false do
+      field :account_id, type: 'long'
+
+      field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].join("\n\n") } do
+        field :stemmed, type: 'text', analyzer: 'content'
+      end
+
+      field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }
+      field :created_at, type: 'date'
+    end
+  end
+end
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index 8785df14e..7b46b2228 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -36,7 +36,7 @@ class AboutController < ApplicationController
 
   def initial_state_params
     {
-      settings: {},
+      settings: { known_fediverse: Setting.show_known_fediverse_at_about_page },
       token: current_session&.token,
     }
   end
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index c9725ed00..1efaf619b 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class AccountsController < ApplicationController
+  PAGE_SIZE = 20
+
   include AccountControllerConcern
 
   before_action :set_cache_headers
@@ -17,13 +19,16 @@ class AccountsController < ApplicationController
         end
 
         @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
-        @statuses        = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id])
+        @statuses        = filtered_status_page(params)
         @statuses        = cache_collection(@statuses, Status)
-        @next_url        = next_url unless @statuses.empty?
+        unless @statuses.empty?
+          @older_url        = older_url if @statuses.last.id > filtered_statuses.last.id
+          @newer_url        = newer_url if @statuses.first.id < filtered_statuses.first.id
+        end
       end
 
       format.atom do
-        @entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
+        @entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id])
         render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? }))
       end
 
@@ -70,13 +75,22 @@ class AccountsController < ApplicationController
     @account = Account.find_local!(params[:username])
   end
 
-  def next_url
+  def older_url
+    ::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}")
+    pagination_url(max_id: @statuses.last.id)
+  end
+
+  def newer_url
+    pagination_url(min_id: @statuses.first.id)
+  end
+
+  def pagination_url(max_id: nil, min_id: nil)
     if media_requested?
-      short_account_media_url(@account, max_id: @statuses.last.id)
+      short_account_media_url(@account, max_id: max_id, min_id: min_id)
     elsif replies_requested?
-      short_account_with_replies_url(@account, max_id: @statuses.last.id)
+      short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
     else
-      short_account_url(@account, max_id: @statuses.last.id)
+      short_account_url(@account, max_id: max_id, min_id: min_id)
     end
   end
 
@@ -87,4 +101,12 @@ class AccountsController < ApplicationController
   def replies_requested?
     request.path.ends_with?('/with_replies')
   end
+
+  def filtered_status_page(params)
+    if params[:min_id].present?
+      filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
+    else
+      filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
+    end
+  end
 end
diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb
new file mode 100644
index 000000000..081914016
--- /dev/null
+++ b/app/controllers/activitypub/collections_controller.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+class ActivityPub::CollectionsController < Api::BaseController
+  include SignatureVerification
+
+  before_action :set_account
+  before_action :set_size
+  before_action :set_statuses
+
+  def show
+    render json: collection_presenter,
+           serializer: ActivityPub::CollectionSerializer,
+           adapter: ActivityPub::Adapter,
+           content_type: 'application/activity+json',
+           skip_activities: true
+  end
+
+  private
+
+  def set_account
+    @account = Account.find_local!(params[:account_username])
+  end
+
+  def set_statuses
+    @statuses = scope_for_collection.paginate_by_max_id(20, params[:max_id], params[:since_id])
+    @statuses = cache_collection(@statuses, Status)
+  end
+
+  def set_size
+    case params[:id]
+    when 'featured'
+      @account.pinned_statuses.count
+    else
+      raise ActiveRecord::NotFound
+    end
+  end
+
+  def scope_for_collection
+    case params[:id]
+    when 'featured'
+      @account.statuses.permitted_for(@account, signed_request_account).tap do |scope|
+        scope.merge!(@account.pinned_statuses)
+      end
+    else
+      raise ActiveRecord::NotFound
+    end
+  end
+
+  def collection_presenter
+    ActivityPub::CollectionPresenter.new(
+      id: account_collection_url(@account, params[:id]),
+      type: :ordered,
+      size: @size,
+      items: @statuses
+    )
+  end
+end
diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb
index 76553a162..af51e32d5 100644
--- a/app/controllers/activitypub/inboxes_controller.rb
+++ b/app/controllers/activitypub/inboxes_controller.rb
@@ -11,7 +11,7 @@ class ActivityPub::InboxesController < Api::BaseController
       process_payload
       head 202
     else
-      [signature_verification_failure_reason, 401]
+      render plain: signature_verification_failure_reason, status: 401
     end
   end
 
@@ -28,7 +28,7 @@ class ActivityPub::InboxesController < Api::BaseController
   def upgrade_account
     if signed_request_account.ostatus?
       signed_request_account.update(last_webfingered_at: nil)
-      ResolveRemoteAccountWorker.perform_async(signed_request_account.acct)
+      ResolveAccountWorker.perform_async(signed_request_account.acct)
     end
 
     Pubsubhubbub::UnsubscribeWorker.perform_async(signed_request_account.id) if signed_request_account.subscribed?
diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb
index 9f97ff622..9ed700c1e 100644
--- a/app/controllers/activitypub/outboxes_controller.rb
+++ b/app/controllers/activitypub/outboxes_controller.rb
@@ -1,13 +1,15 @@
 # frozen_string_literal: true
 
 class ActivityPub::OutboxesController < Api::BaseController
+  include SignatureVerification
+
   before_action :set_account
 
   def show
-    @statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
+    @statuses = @account.statuses.permitted_for(@account, signed_request_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
     @statuses = cache_collection(@statuses, Status)
 
-    render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
+    render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
   end
 
   private
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index 487282dc3..ce3208209 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -16,9 +16,11 @@ module Admin
       show_staff_badge
       bootstrap_timeline_accounts
       thumbnail
+      hero
       min_invite_role
       activity_api_enabled
       peers_api_enabled
+      show_known_fediverse_at_about_page
     ).freeze
 
     BOOLEAN_SETTINGS = %w(
@@ -28,10 +30,12 @@ module Admin
       show_staff_badge
       activity_api_enabled
       peers_api_enabled
+      show_known_fediverse_at_about_page
     ).freeze
 
     UPLOAD_SETTINGS = %w(
       thumbnail
+      hero
     ).freeze
 
     def edit
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index 52e68ab35..7b5168b31 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -51,6 +51,10 @@ class Api::BaseController < ApplicationController
     [params[:limit].to_i.abs, default_limit * 2].min
   end
 
+  def truthy_param?(key)
+    ActiveModel::Type::Boolean.new.cast(params[key])
+  end
+
   def current_resource_owner
     @current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
   end
diff --git a/app/controllers/api/salmon_controller.rb b/app/controllers/api/salmon_controller.rb
index 143e9d3cd..ac5f3268d 100644
--- a/app/controllers/api/salmon_controller.rb
+++ b/app/controllers/api/salmon_controller.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Api::SalmonController < Api::BaseController
+  include SignatureVerification
+
   before_action :set_account
   respond_to :txt
 
@@ -9,7 +11,7 @@ class Api::SalmonController < Api::BaseController
       process_salmon
       head 202
     elsif payload.present?
-      [signature_verification_failure_reason, 401]
+      render plain: signature_verification_failure_reason, status: 401
     else
       head 400
     end
diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb
index da534d960..68af22529 100644
--- a/app/controllers/api/v1/accounts/credentials_controller.rb
+++ b/app/controllers/api/v1/accounts/credentials_controller.rb
@@ -20,6 +20,6 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
   private
 
   def account_params
-    params.permit(:display_name, :note, :avatar, :header)
+    params.permit(:display_name, :note, :avatar, :header, :locked)
   end
 end
diff --git a/app/controllers/api/v1/accounts/relationships_controller.rb b/app/controllers/api/v1/accounts/relationships_controller.rb
index 91a942d75..70236d1a8 100644
--- a/app/controllers/api/v1/accounts/relationships_controller.rb
+++ b/app/controllers/api/v1/accounts/relationships_controller.rb
@@ -10,7 +10,7 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
     accounts = Account.where(id: account_ids).select('id')
     # .where doesn't guarantee that our results are in the same order
     # we requested them, so return the "right" order to the requestor.
-    @accounts = accounts.index_by(&:id).values_at(*account_ids)
+    @accounts = accounts.index_by(&:id).values_at(*account_ids).compact
     render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
   end
 
@@ -21,6 +21,6 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
   end
 
   def account_ids
-    @_account_ids ||= Array(params[:id]).map(&:to_i)
+    Array(params[:id]).map(&:to_i)
   end
 end
diff --git a/app/controllers/api/v1/accounts/search_controller.rb b/app/controllers/api/v1/accounts/search_controller.rb
index 11e647c3c..7649da433 100644
--- a/app/controllers/api/v1/accounts/search_controller.rb
+++ b/app/controllers/api/v1/accounts/search_controller.rb
@@ -22,8 +22,4 @@ class Api::V1::Accounts::SearchController < Api::BaseController
       following: truthy_param?(:following)
     )
   end
-
-  def truthy_param?(key)
-    params[key] == 'true'
-  end
 end
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index 095f6937b..1e1511a7b 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -28,9 +28,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
 
   def account_statuses
     default_statuses.tap do |statuses|
-      statuses.merge!(only_media_scope) if params[:only_media]
-      statuses.merge!(pinned_scope) if params[:pinned]
-      statuses.merge!(no_replies_scope) if params[:exclude_replies]
+      statuses.merge!(only_media_scope) if truthy_param?(:only_media)
+      statuses.merge!(pinned_scope) if truthy_param?(:pinned)
+      statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
     end
   end
 
@@ -51,7 +51,13 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
   end
 
   def account_media_status_ids
-    @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
+    # `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
+    # Also, Avoid getting slow by not narrowing down by `statuses.account_id`.
+    # When narrowing down by `statuses.account_id`, `index_statuses_20180106` will be used
+    # and the table will be joined by `Merge Semi Join`, so the query will be slow.
+    Status.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account)
+          .paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
+          .reorder(id: :desc).distinct(:id).pluck(:id)
   end
 
   def pinned_scope
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index 4e73e9e8b..d64325944 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -13,9 +13,9 @@ class Api::V1::AccountsController < Api::BaseController
   end
 
   def follow
-    FollowService.new.call(current_user.account, @account.acct, reblogs: params[:reblogs])
+    FollowService.new.call(current_user.account, @account.acct, reblogs: truthy_param?(:reblogs))
 
-    options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: params[:reblogs] } }, requested_map: { @account.id => false } }
+    options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
 
     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
   end
@@ -26,7 +26,7 @@ class Api::V1::AccountsController < Api::BaseController
   end
 
   def mute
-    MuteService.new.call(current_user.account, @account, notifications: params[:notifications])
+    MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications))
     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
   end
 
diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb
index 9f330f0df..d4e6337e7 100644
--- a/app/controllers/api/v1/media_controller.rb
+++ b/app/controllers/api/v1/media_controller.rb
@@ -27,7 +27,7 @@ class Api::V1::MediaController < Api::BaseController
   private
 
   def media_params
-    params.permit(:file, :description)
+    params.permit(:file, :description, :focus)
   end
 
   def file_type_error
diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb
index 22828217d..f5095e073 100644
--- a/app/controllers/api/v1/reports_controller.rb
+++ b/app/controllers/api/v1/reports_controller.rb
@@ -13,14 +13,14 @@ class Api::V1::ReportsController < Api::BaseController
   end
 
   def create
-    @report = current_account.reports.create!(
-      target_account: reported_account,
+    @report = ReportService.new.call(
+      current_account,
+      reported_account,
       status_ids: reported_status_ids,
-      comment: report_params[:comment]
+      comment: report_params[:comment],
+      forward: report_params[:forward]
     )
 
-    User.staff.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
-
     render json: @report, serializer: REST::ReportSerializer
   end
 
@@ -39,6 +39,6 @@ class Api::V1::ReportsController < Api::BaseController
   end
 
   def report_params
-    params.permit(:account_id, :comment, status_ids: [])
+    params.permit(:account_id, :comment, :forward, status_ids: [])
   end
 end
diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb
index d1b4e0402..99b635ad9 100644
--- a/app/controllers/api/v1/search_controller.rb
+++ b/app/controllers/api/v1/search_controller.rb
@@ -33,12 +33,8 @@ class Api::V1::SearchController < Api::BaseController
     SearchService.new.call(
       params[:q],
       RESULTS_LIMIT,
-      resolving_search?,
+      truthy_param?(:resolve),
       current_account
     )
   end
-
-  def resolving_search?
-    params[:resolve] == 'true'
-  end
 end
diff --git a/app/controllers/api/v1/statuses/pins_controller.rb b/app/controllers/api/v1/statuses/pins_controller.rb
index 3de1009b8..bba6a6f48 100644
--- a/app/controllers/api/v1/statuses/pins_controller.rb
+++ b/app/controllers/api/v1/statuses/pins_controller.rb
@@ -11,12 +11,18 @@ class Api::V1::Statuses::PinsController < Api::BaseController
 
   def create
     StatusPin.create!(account: current_account, status: @status)
+    distribute_add_activity!
     render json: @status, serializer: REST::StatusSerializer
   end
 
   def destroy
     pin = StatusPin.find_by(account: current_account, status: @status)
-    pin&.destroy!
+
+    if pin
+      pin.destroy!
+      distribute_remove_activity!
+    end
+
     render json: @status, serializer: REST::StatusSerializer
   end
 
@@ -25,4 +31,24 @@ class Api::V1::Statuses::PinsController < Api::BaseController
   def set_status
     @status = Status.find(params[:status_id])
   end
+
+  def distribute_add_activity!
+    json = ActiveModelSerializers::SerializableResource.new(
+      @status,
+      serializer: ActivityPub::AddSerializer,
+      adapter: ActivityPub::Adapter
+    ).as_json
+
+    ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
+  end
+
+  def distribute_remove_activity!
+    json = ActiveModelSerializers::SerializableResource.new(
+      @status,
+      serializer: ActivityPub::RemoveSerializer,
+      adapter: ActivityPub::Adapter
+    ).as_json
+
+    ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
+  end
 end
diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb
index 49887778e..d7d70b94d 100644
--- a/app/controllers/api/v1/timelines/public_controller.rb
+++ b/app/controllers/api/v1/timelines/public_controller.rb
@@ -21,15 +21,23 @@ class Api::V1::Timelines::PublicController < Api::BaseController
   end
 
   def public_statuses
-    public_timeline_statuses.paginate_by_max_id(
+    statuses = public_timeline_statuses.paginate_by_max_id(
       limit_param(DEFAULT_STATUSES_LIMIT),
       params[:max_id],
       params[:since_id]
     )
+
+    if truthy_param?(:only_media)
+      # `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
+      status_ids = statuses.joins(:media_attachments).distinct(:id).pluck(:id)
+      statuses.where(id: status_ids)
+    else
+      statuses
+    end
   end
 
   def public_timeline_statuses
-    Status.as_public_timeline(current_account, params[:local])
+    Status.as_public_timeline(current_account, truthy_param?(:local))
   end
 
   def insert_pagination_headers
@@ -37,7 +45,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:local, :limit).merge(core_params)
+    params.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 08db04a39..eb32611ad 100644
--- a/app/controllers/api/v1/timelines/tag_controller.rb
+++ b/app/controllers/api/v1/timelines/tag_controller.rb
@@ -29,16 +29,24 @@ class Api::V1::Timelines::TagController < Api::BaseController
     if @tag.nil?
       []
     else
-      tag_timeline_statuses.paginate_by_max_id(
+      statuses = tag_timeline_statuses.paginate_by_max_id(
         limit_param(DEFAULT_STATUSES_LIMIT),
         params[:max_id],
         params[:since_id]
       )
+
+      if truthy_param?(:only_media)
+        # `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
+        status_ids = statuses.joins(:media_attachments).distinct(:id).pluck(:id)
+        statuses.where(id: status_ids)
+      else
+        statuses
+      end
     end
   end
 
   def tag_timeline_statuses
-    Status.as_tag_timeline(@tag, current_account, params[:local])
+    Status.as_tag_timeline(@tag, current_account, truthy_param?(:local))
   end
 
   def insert_pagination_headers
@@ -46,7 +54,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:local, :limit).merge(core_params)
+    params.permit(:local, :limit, :only_media).merge(core_params)
   end
 
   def next_path
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 276c6b012..fc745eaec 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -15,6 +15,7 @@ class ApplicationController < ActionController::Base
   helper_method :current_flavour
   helper_method :current_skin
   helper_method :single_user_mode?
+  helper_method :use_seamless_external_login?
 
   rescue_from ActionController::RoutingError, with: :not_found
   rescue_from ActiveRecord::RecordNotFound, with: :not_found
@@ -35,7 +36,7 @@ class ApplicationController < ActionController::Base
   end
 
   def store_current_location
-    store_location_for(:user, request.url)
+    store_location_for(:user, request.url) unless request.format == :json
   end
 
   def require_admin!
@@ -145,6 +146,10 @@ class ApplicationController < ActionController::Base
     @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
   end
 
+  def use_seamless_external_login?
+    Devise.pam_authentication || Devise.ldap_authentication
+  end
+
   def current_account
     @current_account ||= current_user.try(:account)
   end
diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb
index 72b8e9dd8..f3e0ae257 100644
--- a/app/controllers/auth/confirmations_controller.rb
+++ b/app/controllers/auth/confirmations_controller.rb
@@ -3,6 +3,7 @@
 class Auth::ConfirmationsController < Devise::ConfirmationsController
   layout 'auth'
 
+  before_action :set_user, only: [:finish_signup]
   before_action :set_pack
 
   private
@@ -10,4 +11,26 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
   def set_pack
     use_pack 'auth'
   end
+
+  # GET/PATCH /users/:id/finish_signup
+  def finish_signup
+    return unless request.patch? && params[:user]
+    if @user.update(user_params)
+      @user.skip_reconfirmation!
+      sign_in(@user, bypass: true)
+      redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions')
+    else
+      @show_errors = true
+    end
+  end
+
+  private
+
+  def set_user
+    @user = current_user
+  end
+
+  def user_params
+    params.require(:user).permit(:email)
+  end
 end
diff --git a/app/controllers/auth/omniauth_callbacks_controller.rb b/app/controllers/auth/omniauth_callbacks_controller.rb
new file mode 100644
index 000000000..bbf63bed3
--- /dev/null
+++ b/app/controllers/auth/omniauth_callbacks_controller.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
+  skip_before_action :verify_authenticity_token
+
+  def self.provides_callback_for(provider)
+    provider_id = provider.to_s.chomp '_oauth2'
+
+    define_method provider do
+      @user = User.find_for_oauth(request.env['omniauth.auth'], current_user)
+
+      if @user.persisted?
+        sign_in_and_redirect @user, event: :authentication
+        set_flash_message(:notice, :success, kind: provider_id.capitalize) if is_navigational_format?
+      else
+        session["devise.#{provider}_data"] = request.env['omniauth.auth']
+        redirect_to new_user_registration_url
+      end
+    end
+  end
+
+  Devise.omniauth_configs.each_key do |provider|
+    provides_callback_for provider
+  end
+
+  def after_sign_in_path_for(resource)
+    if resource.email_verified?
+      root_path
+    else
+      finish_signup_path
+    end
+  end
+end
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index 2b6a1bdbc..9b3ea4f27 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -15,6 +15,11 @@ class Auth::RegistrationsController < Devise::RegistrationsController
 
   protected
 
+  def update_resource(resource, params)
+    params[:password] = nil if Devise.pam_authentication && resource.encrypted_password.blank?
+    super
+  end
+
   def build_resource(hash = nil)
     super(hash)
 
diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb
index f45d77b88..62f3b2eb6 100644
--- a/app/controllers/auth/sessions_controller.rb
+++ b/app/controllers/auth/sessions_controller.rb
@@ -11,6 +11,14 @@ class Auth::SessionsController < Devise::SessionsController
   prepend_before_action :set_pack
   before_action :set_instance_presenter, only: [:new]
 
+  def new
+    Devise.omniauth_configs.each do |provider, config|
+      return redirect_to(omniauth_authorize_path(resource_name, provider)) if config.strategy.redirect_at_sign_in
+    end
+
+    super
+  end
+
   def create
     super do |resource|
       remember_me(resource)
@@ -29,7 +37,11 @@ class Auth::SessionsController < Devise::SessionsController
     if session[:otp_user_id]
       User.find(session[:otp_user_id])
     elsif user_params[:email]
-      User.find_for_authentication(email: user_params[:email])
+      if use_seamless_external_login? && Devise.check_at_sign && user_params[:email].index('@').nil?
+        User.joins(:account).find_by(accounts: { username: user_params[:email] })
+      else
+        User.find_for_authentication(email: user_params[:email])
+      end
     end
   end
 
@@ -47,6 +59,14 @@ class Auth::SessionsController < Devise::SessionsController
     end
   end
 
+  def after_sign_out_path_for(_resource_or_scope)
+    Devise.omniauth_configs.each_value do |config|
+      return root_path if config.strategy.redirect_at_sign_in
+    end
+
+    super
+  end
+
   def two_factor_enabled?
     find_user.try(:otp_required_for_login?)
   end
diff --git a/app/controllers/authorize_follows_controller.rb b/app/controllers/authorize_follows_controller.rb
index eda50e07d..95052df7c 100644
--- a/app/controllers/authorize_follows_controller.rb
+++ b/app/controllers/authorize_follows_controller.rb
@@ -46,7 +46,7 @@ class AuthorizeFollowsController < ApplicationController
   end
 
   def account_from_remote_follow
-    ResolveRemoteAccountService.new.call(acct_without_prefix)
+    ResolveAccountService.new.call(acct_without_prefix)
   end
 
   def acct_param_is_url?
diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb
index a9ea60f7d..e697284a8 100644
--- a/app/controllers/concerns/localized.rb
+++ b/app/controllers/concerns/localized.rb
@@ -17,11 +17,7 @@ module Localized
   end
 
   def default_locale
-    request_locale || env_locale || I18n.default_locale
-  end
-
-  def env_locale
-    ENV['DEFAULT_LOCALE']
+    request_locale || I18n.default_locale
   end
 
   def request_locale
@@ -29,12 +25,10 @@ module Localized
   end
 
   def preferred_locale
-    http_accept_language.preferred_language_from([env_locale]) ||
-      http_accept_language.preferred_language_from(I18n.available_locales)
+    http_accept_language.preferred_language_from(I18n.available_locales)
   end
 
   def compatible_locale
-    http_accept_language.compatible_language_from([env_locale]) ||
-      http_accept_language.compatible_language_from(I18n.available_locales)
+    http_accept_language.compatible_language_from(I18n.available_locales)
   end
 end
diff --git a/app/controllers/concerns/signature_authentication.rb b/app/controllers/concerns/signature_authentication.rb
new file mode 100644
index 000000000..beec93223
--- /dev/null
+++ b/app/controllers/concerns/signature_authentication.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module SignatureAuthentication
+  extend ActiveSupport::Concern
+
+  include SignatureVerification
+
+  def current_account
+    super || signed_request_account
+  end
+end
diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb
index 2baafb5bf..f289228d3 100644
--- a/app/controllers/concerns/signature_verification.rb
+++ b/app/controllers/concerns/signature_verification.rb
@@ -114,7 +114,7 @@ module SignatureVerification
 
   def account_from_key_id(key_id)
     if key_id.start_with?('acct:')
-      ResolveRemoteAccountService.new.call(key_id.gsub(/\Aacct:/, ''))
+      ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, ''))
     elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
       account   = ActivityPub::TagManager.instance.uri_to_resource(key_id, Account)
       account ||= ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false)
diff --git a/app/controllers/concerns/user_tracking_concern.rb b/app/controllers/concerns/user_tracking_concern.rb
index a2510e55f..be10705fc 100644
--- a/app/controllers/concerns/user_tracking_concern.rb
+++ b/app/controllers/concerns/user_tracking_concern.rb
@@ -3,7 +3,6 @@
 module UserTrackingConcern
   extend ActiveSupport::Concern
 
-  REGENERATE_FEED_DAYS = 14
   UPDATE_SIGN_IN_HOURS = 24
 
   included do
@@ -14,25 +13,10 @@ module UserTrackingConcern
 
   def set_user_activity
     return unless user_needs_sign_in_update?
-
-    # Mark as signed-in today
     current_user.update_tracked_fields!(request)
-    ActivityTracker.record('activity:logins', current_user.id)
-
-    # Regenerate feed if needed
-    regenerate_feed! if user_needs_feed_update?
   end
 
   def user_needs_sign_in_update?
     user_signed_in? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < UPDATE_SIGN_IN_HOURS.hours.ago)
   end
-
-  def user_needs_feed_update?
-    current_user.last_sign_in_at < REGENERATE_FEED_DAYS.days.ago
-  end
-
-  def regenerate_feed!
-    Redis.current.setnx("account:#{current_user.account_id}:regeneration", true) && Redis.current.expire("account:#{current_user.account_id}:regeneration", 1.day.seconds)
-    RegenerationWorker.perform_async(current_user.account_id)
-  end
 end
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index 080cbde11..c74d3f86d 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -9,6 +9,8 @@ class FollowerAccountsController < ApplicationController
     respond_to do |format|
       format.html do
         use_pack 'public'
+
+        @relationships = AccountRelationshipsPresenter.new(@follows.map(&:account_id), current_user.account_id) if user_signed_in?
       end
 
       format.json do
diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb
index 74e83ad81..4c1e3f327 100644
--- a/app/controllers/following_accounts_controller.rb
+++ b/app/controllers/following_accounts_controller.rb
@@ -9,6 +9,8 @@ class FollowingAccountsController < ApplicationController
     respond_to do |format|
       format.html do
         use_pack 'public'
+
+        @relationships = AccountRelationshipsPresenter.new(@follows.map(&:target_account_id), current_user.account_id) if user_signed_in?
       end
 
       format.json do
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 7437a647e..a8ec0dcc9 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -35,7 +35,8 @@ class HomeController < ApplicationController
       end
     end
 
-    redirect_to(default_redirect_path)
+    matches = request.path.match(%r{\A/web/timelines/tag/(?<tag>.+)\z})
+    redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path)
   end
 
   def set_pack
diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb
index f652f5ace..88c7232dd 100644
--- a/app/controllers/media_controller.rb
+++ b/app/controllers/media_controller.rb
@@ -3,20 +3,26 @@
 class MediaController < ApplicationController
   include Authorization
 
-  before_action :verify_permitted_status
+  before_action :set_media_attachment
+  before_action :verify_permitted_status!
 
   def show
-    redirect_to media_attachment.file.url(:original)
+    redirect_to @media_attachment.file.url(:original)
+  end
+
+  def player
+    @body_classes = 'player'
+    raise ActiveRecord::RecordNotFound unless @media_attachment.video? || @media_attachment.gifv?
   end
 
   private
 
-  def media_attachment
-    MediaAttachment.attached.find_by!(shortcode: params[:id])
+  def set_media_attachment
+    @media_attachment = MediaAttachment.attached.find_by!(shortcode: params[:id] || params[:medium_id])
   end
 
-  def verify_permitted_status
-    authorize media_attachment.status, :show?
+  def verify_permitted_status!
+    authorize @media_attachment.status, :show?
   rescue Mastodon::NotPermittedError
     # Reraise in order to get a 404 instead of a 403 error code
     raise ActiveRecord::RecordNotFound
diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb
index 9c03ece86..cf8745576 100644
--- a/app/controllers/settings/exports_controller.rb
+++ b/app/controllers/settings/exports_controller.rb
@@ -1,7 +1,19 @@
 # frozen_string_literal: true
 
 class Settings::ExportsController < Settings::BaseController
+  include Authorization
+
   def show
-    @export = Export.new(current_account)
+    @export  = Export.new(current_account)
+    @backups = current_user.backups
+  end
+
+  def create
+    authorize :backup, :create?
+
+    backup = current_user.backups.create!
+    BackupWorker.perform_async(backup.id)
+
+    redirect_to settings_export_path
   end
 end
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index 7cd1abe0c..c853b5ab7 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -36,6 +36,7 @@ class Settings::PreferencesController < Settings::BaseController
       :setting_favourite_modal,
       :setting_delete_modal,
       :setting_auto_play_gif,
+      :setting_display_sensitive_media,
       :setting_reduce_motion,
       :setting_system_font_ui,
       :setting_noindex,
diff --git a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
index f1fa03f0a..8518c61ee 100644
--- a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb
@@ -3,6 +3,8 @@
 module Settings
   module TwoFactorAuthentication
     class ConfirmationsController < BaseController
+      before_action :ensure_otp_secret
+
       def new
         prepare_two_factor_form
       end
@@ -34,6 +36,10 @@ module Settings
         @provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Rails.configuration.x.local_domain)
         @qrcode = RQRCode::QRCode.new(@provision_url)
       end
+
+      def ensure_otp_secret
+        redirect_to settings_two_factor_authentication_path unless current_user.otp_secret
+      end
     end
   end
 end
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index d67fac0e5..61ffb97d9 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
 class StatusesController < ApplicationController
+  include SignatureAuthentication
   include Authorization
 
   layout 'public'
diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb
index b597ba4bb..e2ea45c83 100644
--- a/app/controllers/stream_entries_controller.rb
+++ b/app/controllers/stream_entries_controller.rb
@@ -10,6 +10,7 @@ class StreamEntriesController < ApplicationController
   before_action :set_stream_entry
   before_action :set_link_headers
   before_action :check_account_suspension
+  before_action :set_cache_headers
 
   def show
     respond_to do |format|
@@ -20,6 +21,10 @@ class StreamEntriesController < ApplicationController
       end
 
       format.atom do
+        unless @stream_entry.hidden?
+          skip_session!
+          expires_in 3.minutes, public: true
+        end
         render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true))
       end
     end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8ed5c8bda..bab4615a1 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -22,10 +22,18 @@ module ApplicationHelper
   end
 
   def add_rtl_body_class(other_classes)
-    other_classes = "#{other_classes} rtl" if [:ar, :fa, :he].include?(I18n.locale)
+    other_classes = "#{other_classes} rtl" if locale_direction == 'rtl'
     other_classes
   end
 
+  def locale_direction
+    if [:ar, :fa, :he].include?(I18n.locale)
+      'rtl'
+    else
+      'ltr'
+    end
+  end
+
   def favicon_path
     env_suffix = Rails.env.production? ? '' : '-dev'
     "/favicon#{env_suffix}.ico"
diff --git a/app/helpers/instance_helper.rb b/app/helpers/instance_helper.rb
index 22a19c52b..dd0b25f3e 100644
--- a/app/helpers/instance_helper.rb
+++ b/app/helpers/instance_helper.rb
@@ -2,7 +2,7 @@
 
 module InstanceHelper
   def site_title
-    Setting.site_title.presence || site_hostname
+    Setting.site_title
   end
 
   def site_hostname
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index a63eb5e43..a2f5917f9 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -16,6 +16,7 @@ module SettingsHelper
     he: 'עברית',
     hr: 'Hrvatski',
     hu: 'Magyar',
+    hy: 'Հայերեն',
     id: 'Bahasa Indonesia',
     io: 'Ido',
     it: 'Italiano',
diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb
index 445114985..54b92bdf4 100644
--- a/app/helpers/stream_entries_helper.rb
+++ b/app/helpers/stream_entries_helper.rb
@@ -8,6 +8,27 @@ module StreamEntriesHelper
     account.display_name.presence || account.username
   end
 
+  def account_description(account)
+    prepend_str = [
+      [
+        number_to_human(account.statuses_count, strip_insignificant_zeros: true),
+        t('accounts.posts'),
+      ].join(' '),
+
+      [
+        number_to_human(account.following_count, strip_insignificant_zeros: true),
+        t('accounts.following'),
+      ].join(' '),
+
+      [
+        number_to_human(account.followers_count, strip_insignificant_zeros: true),
+        t('accounts.followers'),
+      ].join(' '),
+    ].join(', ')
+
+    [prepend_str, account.note].join(' · ')
+  end
+
   def stream_link_target
     embedded_view? ? '_blank' : nil
   end
diff --git a/app/javascript/core/admin.js b/app/javascript/core/admin.js
index c0bd09bdd..b4125e84e 100644
--- a/app/javascript/core/admin.js
+++ b/app/javascript/core/admin.js
@@ -30,13 +30,13 @@ delegate(document, batchCheckboxClassName, 'change', () => {
 });
 
 delegate(document, '.media-spoiler-show-button', 'click', () => {
-  [].forEach.call(document.querySelectorAll('.activity-stream .media-spoiler-wrapper'), (content) => {
-    content.classList.add('media-spoiler-wrapper__visible');
+  [].forEach.call(document.querySelectorAll('button.media-spoiler'), (element) => {
+    element.click();
   });
 });
 
 delegate(document, '.media-spoiler-hide-button', 'click', () => {
-  [].forEach.call(document.querySelectorAll('.activity-stream .media-spoiler-wrapper'), (content) => {
-    content.classList.remove('media-spoiler-wrapper__visible');
+  [].forEach.call(document.querySelectorAll('.spoiler-button.spoiler-button--visible button'), (element) => {
+    element.click();
   });
 });
diff --git a/app/javascript/flavours/glitch/components/intersection_observer_article.js b/app/javascript/flavours/glitch/components/intersection_observer_article.js
index 8b06f9a8c..f7f6b0a53 100644
--- a/app/javascript/flavours/glitch/components/intersection_observer_article.js
+++ b/app/javascript/flavours/glitch/components/intersection_observer_article.js
@@ -1,15 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import ImmutablePureComponent from 'react-immutable-pure-component';
 import scheduleIdleTask from 'flavours/glitch/util/schedule_idle_task';
 import getRectFromEntry from 'flavours/glitch/util/get_rect_from_entry';
-import { is } from 'immutable';
 
-// Diff these props in the "rendered" state
-const updateOnPropsForRendered = ['id', 'index', 'listLength'];
-// Diff these props in the "unrendered" state
-const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight'];
-
-export default class IntersectionObserverArticle extends React.Component {
+export default class IntersectionObserverArticle extends ImmutablePureComponent {
 
   static propTypes = {
     intersectionObserverWrapper: PropTypes.object.isRequired,
@@ -27,15 +22,18 @@ export default class IntersectionObserverArticle extends React.Component {
   }
 
   shouldComponentUpdate (nextProps, nextState) {
-    const isUnrendered = !this.state.isIntersecting && (this.state.isHidden || this.props.cachedHeight);
-    const willBeUnrendered = !nextState.isIntersecting && (nextState.isHidden || nextProps.cachedHeight);
-    if (!!isUnrendered !== !!willBeUnrendered) {
-      // If we're going from rendered to unrendered (or vice versa) then update
+    if (!nextState.isIntersecting && nextState.isHidden) {
+      // It's only if we're not intersecting (i.e. offscreen) and isHidden is true
+      // that either "isIntersecting" or "isHidden" matter, and then they're
+      // the only things that matter (and updated ARIA attributes).
+      return this.state.isIntersecting || !this.state.isHidden || nextProps.listLength !== this.props.listLength;
+    } else if (nextState.isIntersecting && !this.state.isIntersecting) {
+      // If we're going from a non-intersecting state to an intersecting state,
+      // (i.e. offscreen to onscreen), then we definitely need to re-render
       return true;
     }
-    // Otherwise, diff based on props
-    const propsToDiff = isUnrendered ? updateOnPropsForUnrendered : updateOnPropsForRendered;
-    return !propsToDiff.every(prop => is(nextProps[prop], this.props[prop]));
+    // Otherwise, diff based on "updateOnProps" and "updateOnStates"
+    return super.shouldComponentUpdate(nextProps, nextState);
   }
 
   componentDidMount () {
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index feffc2d59..d63b6a639 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -47,7 +47,6 @@ export default class Status extends ImmutablePureComponent {
 
   state = {
     isExpanded: null,
-    markedForDelete: false,
   }
 
   // Avoid checking props that are functions (and whose equality will always
@@ -67,7 +66,6 @@ export default class Status extends ImmutablePureComponent {
 
   updateOnStates = [
     'isExpanded',
-    'markedForDelete',
   ]
 
   //  If our settings have changed to disable collapsed statuses, then we
@@ -382,7 +380,6 @@ export default class Status extends ImmutablePureComponent {
     const computedClass = classNames('status', `status-${status.get('visibility')}`, {
       collapsed: isExpanded === false,
       'has-background': isExpanded === false && background,
-      'marked-for-delete': this.state.markedForDelete,
       muted,
     }, 'focusable');
 
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index 767e4da47..f346bd108 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -15,6 +15,7 @@ const messages = defineMessages({
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
   follow: { id: 'account.follow', defaultMessage: 'Follow' },
   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
+  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
 });
 
 @injectIntl
@@ -23,6 +24,7 @@ export default class Header extends ImmutablePureComponent {
   static propTypes = {
     account: ImmutablePropTypes.map,
     onFollow: PropTypes.func.isRequired,
+    onBlock: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
@@ -35,11 +37,21 @@ export default class Header extends ImmutablePureComponent {
 
     let displayName = account.get('display_name_html');
     let info        = '';
+    let mutingInfo  = '';
     let actionBtn   = '';
 
     if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
       info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
     }
+    else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
+      info = <span className='account--follows-info'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>;
+    }
+
+    if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
+      mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>;
+    } else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
+      mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>;
+    }
 
     if (me !== account.get('id')) {
       if (account.getIn(['relationship', 'requested'])) {
@@ -54,6 +66,12 @@ export default class Header extends ImmutablePureComponent {
             <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
           </div>
         );
+      } else if (account.getIn(['relationship', 'blocking'])) {
+        actionBtn = (
+          <div className='account--action-button'>
+            <IconButton size={26} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
+          </div>
+        );
       }
     }
 
@@ -78,6 +96,7 @@ export default class Header extends ImmutablePureComponent {
             <div className='account__header__content' dangerouslySetInnerHTML={{ __html: emojify(text) }} />
 
             {info}
+            {mutingInfo}
             {actionBtn}
           </div>
         </div>
diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
index 4ad677fbe..092034664 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js
@@ -76,6 +76,7 @@ export default class Header extends ImmutablePureComponent {
         <InnerHeader
           account={account}
           onFollow={this.handleFollow}
+          onBlock={this.handleBlock}
         />
 
         <ActionBar
diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss
index 31c079cc5..d97e23696 100644
--- a/app/javascript/flavours/glitch/styles/about.scss
+++ b/app/javascript/flavours/glitch/styles/about.scss
@@ -1,4 +1,183 @@
+$maximum-width: 1235px;
+$fluid-breakpoint: $maximum-width + 20px;
+$column-breakpoint: 700px;
+$small-breakpoint: 960px;
+
+.container {
+  box-sizing: border-box;
+  max-width: $maximum-width;
+  margin: 0 auto;
+  position: relative;
+
+  @media screen and (max-width: $fluid-breakpoint) {
+    width: 100%;
+    padding: 0 10px;
+  }
+}
+
 .landing-page {
+  .grid {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: 1fr 2fr;
+    grid-auto-columns: 25%;
+    grid-auto-rows: max-content;
+
+    .column-0 {
+      display: none;
+    }
+
+    .column-1 {
+      grid-column: 1;
+      grid-row: 1;
+    }
+
+    .column-2 {
+      grid-column: 2;
+      grid-row: 1;
+    }
+
+    .column-3 {
+      grid-column: 3;
+      grid-row: 1 / 3;
+    }
+
+    .column-4 {
+      grid-column: 1 / 3;
+      grid-row: 2;
+    }
+  }
+
+  @media screen and (max-width: $small-breakpoint) {
+    .grid {
+      grid-template-columns: 40% 60%;
+
+      .column-0 {
+        display: none;
+      }
+
+      .column-1 {
+        grid-column: 1;
+        grid-row: 1;
+
+        &.non-preview .landing-page__forms {
+          height: 100%;
+        }
+      }
+
+      .column-2 {
+        grid-column: 2;
+        grid-row: 1 / 3;
+
+        &.non-preview {
+          grid-column: 2;
+          grid-row: 1;
+        }
+      }
+
+      .column-3 {
+        grid-column: 1;
+        grid-row: 2 / 4;
+      }
+
+      .column-4 {
+        grid-column: 2;
+        grid-row: 3;
+
+        &.non-preview {
+          grid-column: 1 / 3;
+          grid-row: 2;
+        }
+      }
+    }
+  }
+
+  @media screen and (max-width: $column-breakpoint) {
+    .grid {
+      grid-template-columns: auto;
+
+      .column-0 {
+        display: block;
+        grid-column: 1;
+        grid-row: 1;
+      }
+
+      .column-1 {
+        grid-column: 1;
+        grid-row: 3;
+
+        .brand {
+          display: none;
+        }
+      }
+
+      .column-2 {
+        grid-column: 1;
+        grid-row: 2;
+
+        .landing-page__logo,
+        .landing-page__call-to-action {
+          display: none;
+        }
+
+        &.non-preview {
+          grid-column: 1;
+          grid-row: 2;
+        }
+      }
+
+      .column-3 {
+        grid-column: 1;
+        grid-row: 5;
+      }
+
+      .column-4 {
+        grid-column: 1;
+        grid-row: 4;
+
+        &.non-preview {
+          grid-column: 1;
+          grid-row: 4;
+        }
+      }
+    }
+  }
+
+  .column-flex {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .separator-or {
+    position: relative;
+    margin: 40px 0;
+    text-align: center;
+
+    &::before {
+      content: "";
+      display: block;
+      width: 100%;
+      height: 0;
+      border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
+      position: absolute;
+      top: 50%;
+      left: 0;
+    }
+
+    span {
+      display: inline-block;
+      background: $ui-base-color;
+      font-size: 12px;
+      font-weight: 500;
+      color: $ui-primary-color;
+      text-transform: uppercase;
+      position: relative;
+      z-index: 1;
+      padding: 0 8px;
+      cursor: default;
+    }
+  }
+
   p,
   li {
     font-family: 'mastodon-font-sans-serif', sans-serif;
@@ -116,10 +295,14 @@
   }
 
   hr {
-    border-color: rgba($ui-base-lighter-color, .6);
+    width: 100%;
+    height: 0;
+    border: 0;
+    border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
+    margin: 20px 0;
   }
 
-  .container {
+  .container-alt {
     width: 100%;
     box-sizing: border-box;
     max-width: 800px;
@@ -152,24 +335,20 @@
         }
       }
     }
+  }
 
-    .mascot-container {
-      max-width: 800px;
-      margin: 0 auto;
-      position: absolute;
-      top: 0;
-      left: 0;
-      right: 0;
-      height: 100%;
+  .brand {
+    a {
+      padding-left: 0;
+      padding-right: 0;
+      color: $white;
     }
 
-    .mascot {
-      position: absolute;
-      bottom: -14px;
-      width: auto;
-      height: auto;
-      left: 60px;
-      z-index: 3;
+    img {
+      height: 32px;
+      position: relative;
+      top: 4px;
+      left: -10px;
     }
   }
 
@@ -177,7 +356,7 @@
     line-height: 30px;
     overflow: hidden;
 
-    .container {
+    .container-alt {
       display: flex;
       justify-content: space-between;
     }
@@ -203,21 +382,6 @@
         }
       }
 
-      .brand {
-        a {
-          padding-left: 0;
-          padding-right: 0;
-          color: $white;
-        }
-
-        img {
-          height: 32px;
-          position: relative;
-          top: 4px;
-          left: -10px;
-        }
-      }
-
       ul {
         list-style: none;
         margin: 0;
@@ -243,53 +407,6 @@
       align-items: center;
       position: relative;
 
-      .floats {
-        position: absolute;
-        width: 100%;
-        height: 100%;
-        top: 0;
-        left: 0;
-
-        div {
-          position: absolute;
-          transition: all 0.1s linear;
-          animation-name: floating;
-          animation-iteration-count: infinite;
-          animation-direction: alternate;
-          animation-timing-function: ease-in-out;
-          z-index: 2;
-        }
-
-        .float-1 {
-          width: 324px;
-          height: 170px;
-          right: -120px;
-          bottom: 0;
-          animation-duration: 3s;
-          background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 447.1875 234.375" height="170" width="324"><path fill="#{hex-color($ui-base-lighter-color)}" d="M21.69 233.366c-6.45-1.268-13.347-5.63-16.704-10.564-10.705-15.734-1.513-37.724 18.632-44.57l4.8-1.632.173-17.753c.146-14.77.515-19.063 2.2-25.55 6.736-25.944 24.46-46.032 47.766-54.137 11.913-4.143 19.558-5.366 34.178-5.47l13.828-.096V71.12c0-4.755 2.853-17.457 5.238-23.327 8.588-21.137 26.735-35.957 52.153-42.593 23.248-6.07 50.153-6.415 71.863-.923 11.14 2.82 25.686 9.957 33.857 16.615 19.335 15.756 31.82 41.05 35.183 71.275.59 5.305.672 5.435 3.11 4.926 11.833-2.474 30.4-3.132 40.065-1.42 24.388 4.32 40.568 19.076 47.214 43.058 2.16 7.8 3.953 23.894 3.59 32.237l-.24 5.498 5.156 1.317c6.392 1.633 14.55 7.098 18.003 12.062 1.435 2.062 3.305 6.597 4.156 10.078 1.428 5.84 1.43 6.8.04 12.44-1.807 7.318-5.672 13.252-10.872 16.694-8.508 5.63 3.756 5.33-211.916 5.216-108.56-.056-199.22-.464-201.47-.906z"/></svg>');
-        }
-
-        .float-2 {
-          width: 241px;
-          height: 100px;
-          right: 210px;
-          bottom: 0;
-          animation-duration: 3.5s;
-          animation-delay: 0.2s;
-          background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 536.25 222.1875" height="100" width="241"><path fill="#{hex-color($ui-base-lighter-color)}" d="M42.626 221.23c-14.104-1.174-26.442-5.133-32.825-10.534-4.194-3.548-7.684-10.66-8.868-18.075-1.934-12.102.633-22.265 7.528-29.81 7.61-8.328 19.998-12.76 39.855-14.257l8.47-.638-2.08-6.223c-4.826-14.422-6.357-24.813-6.37-43.255-.012-14.923.28-18.513 2.1-25.724 2.283-9.048 8.483-23.034 13.345-30.1 14.76-21.45 43.505-38.425 70.535-41.65 30.628-3.655 64.47 12.073 89.668 41.673l5.955 6.995 2.765-4.174c1.52-2.296 5.74-6.93 9.376-10.295 18.382-17.02 43.436-20.676 73.352-10.705 12.158 4.052 21.315 9.53 29.64 17.733 12.752 12.562 18.16 25.718 18.19 44.26l.02 10.98 2.312-3.01c15.64-20.365 42.29-20.485 62.438-.28 3.644 3.653 7.558 8.593 8.697 10.976 4.895 10.24 5.932 25.688 2.486 37.046-.76 2.507-1.388 4.816-1.393 5.13-.006.316 6.845.87 15.224 1.234 53.06 2.297 76.356 12.98 81.817 37.526 3.554 15.973-3.71 28.604-19.566 34.02-4.554 1.555-17.922 1.655-234.517 1.757-126.327.06-233.497-.21-238.154-.597z"/></svg>');
-        }
-
-        .float-3 {
-          width: 267px;
-          height: 140px;
-          right: 110px;
-          top: -30px;
-          animation-duration: 4s;
-          animation-delay: 0.5s;
-          background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 388.125 202.5" height="140" width="267"><path fill="#{hex-color($ui-base-lighter-color)}" d="M181.37 201.458c-17.184-1.81-36.762-8.944-49.523-18.05l-5.774-4.12-8.074 2.63c-11.468 3.738-21.382 4.962-35.815 4.422-14.79-.554-24.577-2.845-36.716-8.594-15.483-7.332-28.498-19.98-35.985-34.968C2.44 128.675-.94 108.435.9 91.356c3.362-31.234 18.197-53.698 43.63-66.074 12.803-6.23 22.384-8.55 37.655-9.122 14.433-.54 24.347.684 35.814 4.42l8.073 2.633 5.635-4.01c24.81-17.656 60.007-23.332 92.914-14.985 10.11 2.565 25.498 9.62 33.102 15.178l5.068 3.704 7.632-2.564c10.89-3.66 21.086-4.916 35.516-4.376 45.816 1.716 76.422 30.03 81.285 75.196 1.84 17.08-1.54 37.32-8.585 51.422-7.487 14.99-20.502 27.636-35.984 34.968-12.14 5.75-21.926 8.04-36.716 8.593-14.43.54-24.626-.716-35.516-4.376l-7.632-2.564-5.068 3.704c-12.844 9.387-32.714 16.488-51.545 18.42-10.607 1.09-13.916 1.08-24.81-.066z"/></svg>');
-        }
-      }
-
       .heading {
         position: relative;
         z-index: 4;
@@ -346,18 +463,18 @@
     background: darken($ui-base-color, 4%);
     padding: 20px 0;
 
-    .container {
+    .container-alt {
       position: relative;
       padding-right: 280px + 15px;
     }
 
-    .information-board-sections {
+    &__sections {
       display: flex;
       justify-content: space-between;
       flex-wrap: wrap;
     }
 
-    .section {
+    &__section {
       flex: 1 0 0;
       font-family: 'mastodon-font-sans-serif', sans-serif;
       font-size: 16px;
@@ -382,6 +499,10 @@
         font-size: 32px;
         line-height: 48px;
       }
+
+      @media screen and (max-width: $column-breakpoint) {
+        text-align: center;
+      }
     }
 
     .panel {
@@ -428,15 +549,12 @@
         height: 80px;
         margin: 0 auto;
         margin-bottom: 15px;
-        @include avatar-size(80px);
 
         img {
           display: block;
           width: 80px;
           height: 80px;
           border-radius: 48px;
-          @include avatar-radius();
-          @include avatar-size(80px);
         }
       }
 
@@ -463,111 +581,298 @@
     }
   }
 
-  .features {
-    padding: 50px 0;
+  &.alternative {
+    padding: 10px 0;
 
-    .container {
-      display: flex;
-    }
+    .brand {
+      text-align: center;
+      padding: 30px 0;
+      margin-bottom: 10px;
 
-    #mastodon-timeline {
-      display: flex;
-      -webkit-overflow-scrolling: touch;
-      -ms-overflow-style: -ms-autohiding-scrollbar;
-      font-family: 'mastodon-font-sans-serif', sans-serif;
-      font-size: 13px;
-      line-height: 18px;
-      font-weight: 400;
-      color: $primary-text-color;
-      width: 330px;
-      margin-right: 30px;
-      flex: 0 0 auto;
-      background: $ui-base-color;
-      overflow: hidden;
-      border-radius: 4px;
-      box-shadow: 0 0 6px rgba($black, 0.1);
+      img {
+        position: static;
+        padding: 10px 0;
+      }
 
-      .column-header {
-        color: inherit;
-        font-family: inherit;
-        font-size: 16px;
-        line-height: inherit;
-        font-weight: inherit;
-        margin: 0;
-        padding: 15px;
+      @media screen and (max-width: $small-breakpoint) {
+        padding: 15px 0;
       }
 
-      .column {
+      @media screen and (max-width: $column-breakpoint) {
         padding: 0;
-        border-radius: 4px;
-        overflow: hidden;
+        margin-bottom: -10px;
       }
+    }
+  }
 
-      .scrollable {
-        height: 400px;
+  &__information,
+  &__forms {
+    padding: 20px;
+  }
+
+  &__call-to-action {
+    background: darken($ui-base-color, 4%);
+    border-radius: 4px;
+    padding: 25px 40px;
+    overflow: hidden;
+
+    .row {
+      display: flex;
+      flex-direction: row-reverse;
+      flex-wrap: wrap;
+      justify-content: space-between;
+      align-items: center;
+    }
+
+    .row__information-board {
+      display: flex;
+      justify-content: flex-end;
+      align-items: flex-end;
+
+      .information-board__section {
+        flex: 1 0 auto;
+        padding: 0 10px;
       }
+    }
+
+    .row__mascot {
+      flex: 1;
+      margin: 10px -50px 0 0;
+    }
+  }
+
+  &__logo {
+    margin-right: 20px;
+
+    img {
+      height: 50px;
+      width: auto;
+      mix-blend-mode: lighten;
+    }
+  }
+
+  &__information {
+    padding: 45px 40px;
+    margin-bottom: 10px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    @media screen and (max-width: $column-breakpoint) {
+      padding: 25px 20px;
+    }
+  }
+
+  &__information,
+  &__forms,
+  #mastodon-timeline {
+    box-sizing: border-box;
+    background: $ui-base-color;
+    border-radius: 4px;
+    box-shadow: 0 0 6px rgba($black, 0.1);
+  }
+
+  &__mascot {
+    height: 104px;
+    position: relative;
+    left: -40px;
+    bottom: 25px;
+
+    img {
+      height: 190px;
+      width: auto;
+    }
+  }
+
+  &__short-description {
+    .row {
+      display: flex;
+      flex-wrap: wrap;
+      align-items: center;
+      margin-bottom: 40px;
+    }
 
-      p {
-        font-size: inherit;
-        line-height: inherit;
-        font-weight: inherit;
-        color: $primary-text-color;
+    @media screen and (max-width: $column-breakpoint) {
+      .row {
         margin-bottom: 20px;
+      }
+    }
 
-        &:last-child {
-          margin-bottom: 0;
-        }
+    p a {
+      color: $ui-secondary-color;
+    }
 
-        a {
+    h1 {
+      font-weight: 500;
+      color: $primary-text-color;
+      margin-bottom: 0;
+
+      small {
+        color: $ui-primary-color;
+
+        span {
           color: $ui-secondary-color;
-          text-decoration: none;
         }
       }
     }
 
-    .about-mastodon {
-      max-width: 675px;
+    p:last-child {
+      margin-bottom: 0;
+    }
+  }
 
-      p {
-        margin-bottom: 20px;
+  &__hero {
+    margin-bottom: 10px;
+
+    img {
+      display: block;
+      margin: 0;
+      max-width: 100%;
+      height: auto;
+      border-radius: 4px;
+    }
+  }
+
+  &__forms {
+    height: 100%;
+
+    @media screen and (max-width: $small-breakpoint) {
+      height: auto;
+    }
+
+    @media screen and (max-width: $column-breakpoint) {
+      background: transparent;
+      box-shadow: none;
+      padding: 0 20px;
+      margin-top: 30px;
+      margin-bottom: 40px;
+
+      .separator-or {
+        span {
+          background: darken($ui-base-color, 8%);
+        }
       }
+    }
 
-      .features-list {
-        margin-top: 20px;
+    hr {
+      margin: 40px 0;
+    }
 
-        .features-list__row {
-          display: flex;
-          padding: 10px 0;
-          justify-content: space-between;
+    .button {
+      display: block;
+    }
 
-          &:first-child {
-            padding-top: 0;
-          }
+    .subtle-hint a {
+      text-decoration: none;
 
-          .visual {
-            flex: 0 0 auto;
-            display: flex;
-            align-items: center;
-            margin-left: 15px;
+      &:hover,
+      &:focus,
+      &:active {
+        text-decoration: underline;
+      }
+    }
+  }
 
-            .fa {
-              display: block;
-              color: $ui-primary-color;
-              font-size: 48px;
-            }
-          }
+  #mastodon-timeline {
+    display: flex;
+    -webkit-overflow-scrolling: touch;
+    -ms-overflow-style: -ms-autohiding-scrollbar;
+    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-size: 13px;
+    line-height: 18px;
+    font-weight: 400;
+    color: $primary-text-color;
+    width: 100%;
+    flex: 1 1 auto;
+    overflow: hidden;
+    height: 100%;
 
-          .text {
-            font-size: 16px;
-            line-height: 30px;
-            color: $ui-primary-color;
+    .column-header {
+      color: inherit;
+      font-family: inherit;
+      font-size: 16px;
+      line-height: inherit;
+      font-weight: inherit;
+    }
 
-            h6 {
-              font-size: inherit;
-              line-height: inherit;
-              margin-bottom: 0;
-            }
-          }
+    .column {
+      padding: 0;
+      border-radius: 4px;
+      overflow: hidden;
+      width: 100%;
+    }
+
+    .scrollable {
+      height: 400px;
+    }
+
+    p {
+      font-size: inherit;
+      line-height: inherit;
+      font-weight: inherit;
+      color: $primary-text-color;
+      margin-bottom: 20px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      a {
+        color: $ui-secondary-color;
+        text-decoration: none;
+      }
+    }
+
+    @media screen and (max-width: $column-breakpoint) {
+      height: 90vh;
+    }
+  }
+
+  &__features {
+    .features-list {
+      margin: 40px 0 !important;
+    }
+
+    &__action {
+      text-align: center;
+    }
+  }
+
+  .features-list {
+    margin-top: 20px;
+
+    .features-list__row {
+      display: flex;
+      padding: 10px 0;
+      justify-content: space-between;
+
+      &:first-child {
+        padding-top: 0;
+      }
+
+      .visual {
+        flex: 0 0 auto;
+        display: flex;
+        align-items: center;
+        margin-left: 15px;
+
+        .fa {
+          display: block;
+          color: $ui-primary-color;
+          font-size: 48px;
+        }
+      }
+
+      .text {
+        font-size: 16px;
+        line-height: 30px;
+        color: $ui-primary-color;
+
+        h6 {
+          font-size: inherit;
+          line-height: inherit;
+          margin-bottom: 0;
         }
       }
     }
@@ -603,21 +908,31 @@
     }
   }
 
+  &__footer {
+    margin-top: 10px;
+    text-align: center;
+    color: $ui-base-lighter-color;
+
+    p {
+      font-size: 14px;
+
+      a {
+        color: inherit;
+        text-decoration: underline;
+      }
+    }
+  }
+
   @media screen and (max-width: 840px) {
-    .container {
+    .container-alt {
       padding: 0 20px;
     }
 
     .information-board {
-
-      .container {
+      .container-alt {
         padding-right: 20px;
       }
 
-      .section {
-        text-align: center;
-      }
-
       .panel {
         position: static;
         margin-top: 20px;
@@ -629,16 +944,6 @@
         }
       }
     }
-
-    .header-wrapper .mascot {
-      left: 20px;
-    }
-  }
-
-  @media screen and (max-width: 689px) {
-    .header-wrapper .mascot {
-      display: none;
-    }
   }
 
   @media screen and (max-width: 675px) {
@@ -654,13 +959,12 @@
       }
     }
 
-    .header .container,
-    .features .container {
+    .header .container-alt,
+    .features .container-alt {
       display: block;
     }
 
     .header {
-
       .links {
         padding-top: 15px;
         background: darken($ui-base-color, 4%);
@@ -685,10 +989,6 @@
         margin-top: 30px;
         padding: 0;
 
-        .floats {
-          display: none;
-        }
-
         .heading {
           padding: 30px 20px;
           text-align: center;
@@ -703,16 +1003,6 @@
         }
       }
     }
-
-    .features #mastodon-timeline {
-      height: 70vh;
-      width: 100%;
-      margin-bottom: 50px;
-
-      .column {
-        width: 100%;
-      }
-    }
   }
 
   .cta {
@@ -720,108 +1010,55 @@
   }
 
   &.tag-page {
-    .features {
-      padding: 30px 0;
-
-      .container {
-        max-width: 820px;
-
-        #mastodon-timeline {
-          margin-right: 0;
-          border-top-right-radius: 0;
-        }
-
-        .about-mastodon {
-          .about-hashtag {
-            background: darken($ui-base-color, 4%);
-            padding: 0 20px 20px 30px;
-            border-radius: 0 5px 5px 0;
-
-            .brand {
-              padding-top: 20px;
-              margin-bottom: 20px;
-
-              img {
-                height: 48px;
-                width: auto;
-              }
-            }
-
-            p {
-              strong {
-                color: $ui-secondary-color;
-                font-weight: 700;
-              }
-            }
-
-            .cta {
-              margin: 0;
-
-              .button {
-                margin-right: 4px;
-              }
-            }
-          }
+    .grid {
+      @media screen and (min-width: $small-breakpoint) {
+        grid-template-columns: 33% 67%;
+      }
 
-          .features-list {
-            margin-left: 30px;
-            margin-right: 10px;
-          }
-        }
+      .column-2 {
+        grid-column: 2;
+        grid-row: 1;
       }
     }
 
-    @media screen and (max-width: 675px) {
-      .features {
-        padding: 10px 0;
+    .brand {
+      text-align: unset;
+      padding: 0;
 
-        .container {
-          display: flex;
-          flex-direction: column;
-
-          #mastodon-timeline {
-            order: 2;
-            flex: 0 0 auto;
-            height: 60vh;
-            margin-bottom: 20px;
-            border-top-right-radius: 4px;
-          }
+      img {
+        height: 48px;
+        width: auto;
+      }
+    }
 
-          .about-mastodon {
-            order: 1;
-            flex: 0 0 auto;
-            max-width: 100%;
+    .cta {
+      margin: 0;
 
-            .about-hashtag {
-              background: unset;
-              padding: 0;
-              border-radius: 0;
+      .button {
+        margin-right: 4px;
+      }
+    }
 
-              .cta {
-                margin: 20px 0;
-              }
-            }
+    @media screen and (max-width: $column-breakpoint) {
+      .grid {
+        .column-1 {
+          grid-column: 1;
+          grid-row: 2;
+        }
 
-            .features-list {
-              display: none;
-            }
-          }
+        .column-2 {
+          grid-column: 1;
+          grid-row: 1;
         }
       }
-    }
-  }
-}
 
-@keyframes floating {
-  from {
-    transform: translate(0, 0);
-  }
-
-  65% {
-    transform: translate(0, 4px);
-  }
+      .brand {
+        margin: 0;
+      }
 
-  to {
-    transform: translate(0, -0);
+      .landing-page__features {
+        display: none;
+      }
+    }
   }
 }
diff --git a/app/javascript/flavours/glitch/styles/basics.scss b/app/javascript/flavours/glitch/styles/basics.scss
index b5d77ff63..15fbb1c89 100644
--- a/app/javascript/flavours/glitch/styles/basics.scss
+++ b/app/javascript/flavours/glitch/styles/basics.scss
@@ -118,5 +118,6 @@ button {
     height: 100%;
     align-items: center;
     justify-content: center;
+    outline: 0 !important;
   }
 }
diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss
index 67096018f..2bc894d25 100644
--- a/app/javascript/flavours/glitch/styles/components/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/components/accounts.scss
@@ -357,6 +357,22 @@
   border-radius: 4px;
 }
 
+.account--muting-info {
+  color: $primary-text-color;
+  position: absolute;
+  top: 40px;
+  left: 10px;
+  opacity: 0.7;
+  display: inline-block;
+  vertical-align: top;
+  background-color: rgba($base-overlay-background, 0.4);
+  text-transform: uppercase;
+  font-size: 11px;
+  font-weight: 500;
+  padding: 4px;
+  border-radius: 4px;
+}
+
 .account--action-button {
   position: absolute;
   top: 10px;
diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss
index 6e03650ed..5dcfab4d1 100644
--- a/app/javascript/flavours/glitch/styles/components/columns.scss
+++ b/app/javascript/flavours/glitch/styles/components/columns.scss
@@ -242,14 +242,29 @@
 
 .column-header {
   display: flex;
-  padding: 15px;
   font-size: 16px;
+  padding: 15px;
   background: lighten($ui-base-color, 4%);
   flex: 0 0 auto;
   cursor: pointer;
   position: relative;
   z-index: 2;
   outline: 0;
+  overflow: hidden;
+
+  & > button {
+    margin: 0;
+    border: none;
+    padding: 15px 0 15px 15px;
+    color: inherit;
+    background: transparent;
+    font: inherit;
+    text-align: left;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    white-space: nowrap;
+    flex: 1;
+  }
 
   &.active {
     box-shadow: 0 1px 0 rgba($ui-highlight-color, 0.3);
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index ebc4ee383..3d16c856d 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -40,14 +40,20 @@
     cursor: default;
   }
 
-  &.button-alternative {
+  &.button-primary,
+  &.button-alternative,
+  &.button-secondary,
+  &.button-alternative-2 {
     font-size: 16px;
     line-height: 36px;
     height: auto;
-    color: $ui-base-color;
-    background: $ui-primary-color;
     text-transform: none;
     padding: 4px 16px;
+  }
+
+  &.button-alternative {
+    color: $ui-base-color;
+    background: $ui-primary-color;
 
     &:active,
     &:focus,
@@ -56,6 +62,16 @@
     }
   }
 
+  &.button-alternative-2 {
+    background: $ui-base-lighter-color;
+
+    &:active,
+    &:focus,
+    &:hover {
+      background-color: lighten($ui-base-lighter-color, 4%);
+    }
+  }
+
   &.button-secondary {
     font-size: 16px;
     line-height: 36px;
diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss
index 81d4692a8..4dd748227 100644
--- a/app/javascript/flavours/glitch/styles/components/media.scss
+++ b/app/javascript/flavours/glitch/styles/components/media.scss
@@ -84,8 +84,8 @@
   height: 110px;
 
   .detailed-status & {
-    margin-left: -22px;
-    width: calc(100% + 44px);
+    margin-left: -14px;
+    width: calc(100% + 28px);
     height: 250px;
   }
 
diff --git a/app/javascript/flavours/glitch/styles/containers.scss b/app/javascript/flavours/glitch/styles/containers.scss
index af2589e23..6fa1fa38f 100644
--- a/app/javascript/flavours/glitch/styles/containers.scss
+++ b/app/javascript/flavours/glitch/styles/containers.scss
@@ -1,4 +1,4 @@
-.container {
+.container-alt {
   width: 700px;
   margin: 0 auto;
   margin-top: 40px;
diff --git a/app/javascript/images/icon_file_download.svg b/app/javascript/images/icon_file_download.svg
new file mode 100644
index 000000000..53e97e4f8
--- /dev/null
+++ b/app/javascript/images/icon_file_download.svg
@@ -0,0 +1,4 @@
+<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+    <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
+    <path d="M0 0h24v24H0z" fill="none"/>
+</svg>
\ No newline at end of file
diff --git a/app/javascript/images/mailer/icon_cached.png b/app/javascript/images/mailer/icon_cached.png
index e62c064c8..210833d34 100644
--- a/app/javascript/images/mailer/icon_cached.png
+++ b/app/javascript/images/mailer/icon_cached.png
Binary files differdiff --git a/app/javascript/images/mailer/icon_file_download.png b/app/javascript/images/mailer/icon_file_download.png
new file mode 100644
index 000000000..8a6a8673b
--- /dev/null
+++ b/app/javascript/images/mailer/icon_file_download.png
Binary files differdiff --git a/app/javascript/images/reticle.png b/app/javascript/images/reticle.png
new file mode 100644
index 000000000..998994f5c
--- /dev/null
+++ b/app/javascript/images/reticle.png
Binary files differdiff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 8a35049b3..130b4af23 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -1,6 +1,7 @@
 import api from '../api';
 import { throttle } from 'lodash';
 import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
+import { tagHistory } from '../settings';
 import { useEmoji } from './emojis';
 
 import {
@@ -27,6 +28,9 @@ export const COMPOSE_UPLOAD_UNDO     = 'COMPOSE_UPLOAD_UNDO';
 export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
 export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
 export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
+export const COMPOSE_SUGGESTION_TAGS_UPDATE = 'COMPOSE_SUGGESTION_TAGS_UPDATE';
+
+export const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE';
 
 export const COMPOSE_MOUNT   = 'COMPOSE_MOUNT';
 export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
@@ -92,8 +96,9 @@ export function mentionCompose(account, router) {
 export function submitCompose() {
   return function (dispatch, getState) {
     const status = getState().getIn(['compose', 'text'], '');
+    const media  = getState().getIn(['compose', 'media_attachments']);
 
-    if (!status || !status.length) {
+    if ((!status || !status.length) && media.size === 0) {
       return;
     }
 
@@ -102,7 +107,7 @@ export function submitCompose() {
     api(getState).post('/api/v1/statuses', {
       status,
       in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
-      media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
+      media_ids: media.map(item => item.get('id')),
       sensitive: getState().getIn(['compose', 'sensitive']),
       spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
       visibility: getState().getIn(['compose', 'privacy']),
@@ -111,6 +116,7 @@ export function submitCompose() {
         'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
       },
     }).then(function (response) {
+      dispatch(insertIntoTagHistory(response.data.tags));
       dispatch(submitComposeSuccess({ ...response.data }));
 
       // To make the app more responsive, immediately get the status into the columns
@@ -178,11 +184,11 @@ export function uploadCompose(files) {
   };
 };
 
-export function changeUploadCompose(id, description) {
+export function changeUploadCompose(id, params) {
   return (dispatch, getState) => {
     dispatch(changeUploadComposeRequest());
 
-    api(getState).put(`/api/v1/media/${id}`, { description }).then(response => {
+    api(getState).put(`/api/v1/media/${id}`, params).then(response => {
       dispatch(changeUploadComposeSuccess(response.data));
     }).catch(error => {
       dispatch(changeUploadComposeFail(id, error));
@@ -273,12 +279,22 @@ const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
   dispatch(readyComposeSuggestionsEmojis(token, results));
 };
 
+const fetchComposeSuggestionsTags = (dispatch, getState, token) => {
+  dispatch(updateSuggestionTags(token));
+};
+
 export function fetchComposeSuggestions(token) {
   return (dispatch, getState) => {
-    if (token[0] === ':') {
+    switch (token[0]) {
+    case ':':
       fetchComposeSuggestionsEmojis(dispatch, getState, token);
-    } else {
+      break;
+    case '#':
+      fetchComposeSuggestionsTags(dispatch, getState, token);
+      break;
+    default:
       fetchComposeSuggestionsAccounts(dispatch, getState, token);
+      break;
     }
   };
 };
@@ -308,6 +324,9 @@ export function selectComposeSuggestion(position, token, suggestion) {
       startPosition = position - 1;
 
       dispatch(useEmoji(suggestion));
+    } else if (suggestion[0] === '#') {
+      completion    = suggestion;
+      startPosition = position - 1;
     } else {
       completion    = getState().getIn(['accounts', suggestion, 'acct']);
       startPosition = position;
@@ -322,6 +341,48 @@ export function selectComposeSuggestion(position, token, suggestion) {
   };
 };
 
+export function updateSuggestionTags(token) {
+  return {
+    type: COMPOSE_SUGGESTION_TAGS_UPDATE,
+    token,
+  };
+}
+
+export function updateTagHistory(tags) {
+  return {
+    type: COMPOSE_TAG_HISTORY_UPDATE,
+    tags,
+  };
+}
+
+export function hydrateCompose() {
+  return (dispatch, getState) => {
+    const me = getState().getIn(['meta', 'me']);
+    const history = tagHistory.get(me);
+
+    if (history !== null) {
+      dispatch(updateTagHistory(history));
+    }
+  };
+}
+
+function insertIntoTagHistory(tags) {
+  return (dispatch, getState) => {
+    const state = getState();
+    const oldHistory = state.getIn(['compose', 'tagHistory']);
+    const me = state.getIn(['meta', 'me']);
+    const names = tags.map(({ name }) => name);
+    const intersectedOldHistory = oldHistory.filter(name => !names.includes(name));
+
+    names.push(...intersectedOldHistory.toJS());
+
+    const newHistory = names.slice(0, 1000);
+
+    tagHistory.set(me, newHistory);
+    dispatch(updateTagHistory(newHistory));
+  };
+}
+
 export function mountCompose() {
   return {
     type: COMPOSE_MOUNT,
diff --git a/app/javascript/mastodon/actions/dropdown_menu.js b/app/javascript/mastodon/actions/dropdown_menu.js
new file mode 100644
index 000000000..217ba4e74
--- /dev/null
+++ b/app/javascript/mastodon/actions/dropdown_menu.js
@@ -0,0 +1,10 @@
+export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
+export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
+
+export function openDropdownMenu(id, placement) {
+  return { type: DROPDOWN_MENU_OPEN, id, placement };
+}
+
+export function closeDropdownMenu(id) {
+  return { type: DROPDOWN_MENU_CLOSE, id };
+}
diff --git a/app/javascript/mastodon/actions/push_notifications/registerer.js b/app/javascript/mastodon/actions/push_notifications/registerer.js
index 5f47a5501..51e68cad1 100644
--- a/app/javascript/mastodon/actions/push_notifications/registerer.js
+++ b/app/javascript/mastodon/actions/push_notifications/registerer.js
@@ -1,6 +1,7 @@
 import api from '../../api';
 import { pushNotificationsSetting } from '../../settings';
 import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
+import { me } from '../../initial_state';
 
 // Taken from https://www.npmjs.com/package/web-push
 const urlBase64ToUint8Array = (base64String) => {
@@ -35,7 +36,7 @@ const subscribe = (registration) =>
 const unsubscribe = ({ registration, subscription }) =>
   subscription ? subscription.unsubscribe().then(() => registration) : registration;
 
-const sendSubscriptionToBackend = (getState, subscription, me) => {
+const sendSubscriptionToBackend = (getState, subscription) => {
   const params = { subscription };
 
   if (me) {
@@ -54,7 +55,6 @@ const supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager'
 export function register () {
   return (dispatch, getState) => {
     dispatch(setBrowserSupport(supportsPushNotifications));
-    const me = getState().getIn(['meta', 'me']);
 
     if (me && !pushNotificationsSetting.get(me)) {
       const alerts = getState().getIn(['push_notifications', 'alerts']);
@@ -85,13 +85,13 @@ export function register () {
             } else {
               // Something went wrong, try to subscribe again
               return unsubscribe({ registration, subscription }).then(subscribe).then(
-                subscription => sendSubscriptionToBackend(getState, subscription, me));
+                subscription => sendSubscriptionToBackend(getState, subscription));
             }
           }
 
           // No subscription, try to subscribe
           return subscribe(registration).then(
-            subscription => sendSubscriptionToBackend(getState, subscription, me));
+            subscription => sendSubscriptionToBackend(getState, subscription));
         })
         .then(subscription => {
           // If we got a PushSubscription (and not a subscription object from the backend)
@@ -140,7 +140,6 @@ export function saveSettings() {
     api(getState).put(`/api/web/push_subscriptions/${subscription.get('id')}`, {
       data,
     }).then(() => {
-      const me = getState().getIn(['meta', 'me']);
       if (me) {
         pushNotificationsSetting.set(me, data);
       }
diff --git a/app/javascript/mastodon/actions/reports.js b/app/javascript/mastodon/actions/reports.js
index b19a07285..afa0c3412 100644
--- a/app/javascript/mastodon/actions/reports.js
+++ b/app/javascript/mastodon/actions/reports.js
@@ -10,6 +10,7 @@ export const REPORT_SUBMIT_FAIL    = 'REPORT_SUBMIT_FAIL';
 
 export const REPORT_STATUS_TOGGLE  = 'REPORT_STATUS_TOGGLE';
 export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
+export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE';
 
 export function initReport(account, status) {
   return dispatch => {
@@ -45,6 +46,7 @@ export function submitReport() {
       account_id: getState().getIn(['reports', 'new', 'account_id']),
       status_ids: getState().getIn(['reports', 'new', 'status_ids']),
       comment: getState().getIn(['reports', 'new', 'comment']),
+      forward: getState().getIn(['reports', 'new', 'forward']),
     }).then(response => {
       dispatch(closeModal());
       dispatch(submitReportSuccess(response.data));
@@ -78,3 +80,10 @@ export function changeReportComment(comment) {
     comment,
   };
 };
+
+export function changeReportForward(forward) {
+  return {
+    type: REPORT_FORWARD_CHANGE,
+    forward,
+  };
+};
diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js
index 78c6109f7..73cb106ec 100644
--- a/app/javascript/mastodon/actions/search.js
+++ b/app/javascript/mastodon/actions/search.js
@@ -1,4 +1,5 @@
 import api from '../api';
+import { fetchRelationships } from './accounts';
 
 export const SEARCH_CHANGE = 'SEARCH_CHANGE';
 export const SEARCH_CLEAR  = 'SEARCH_CLEAR';
@@ -38,6 +39,7 @@ export function submitSearch() {
       },
     }).then(response => {
       dispatch(fetchSearchSuccess(response.data));
+      dispatch(fetchRelationships(response.data.accounts.map(item => item.id)));
     }).catch(error => {
       dispatch(fetchSearchFail(error));
     });
diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js
index 2204e0b14..073f09883 100644
--- a/app/javascript/mastodon/actions/statuses.js
+++ b/app/javascript/mastodon/actions/statuses.js
@@ -23,6 +23,9 @@ export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST';
 export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS';
 export const STATUS_UNMUTE_FAIL    = 'STATUS_UNMUTE_FAIL';
 
+export const STATUS_REVEAL = 'STATUS_REVEAL';
+export const STATUS_HIDE   = 'STATUS_HIDE';
+
 export function fetchStatusRequest(id, skipLoading) {
   return {
     type: STATUS_FETCH_REQUEST,
@@ -215,3 +218,25 @@ export function unmuteStatusFail(id, error) {
     error,
   };
 };
+
+export function hideStatus(ids) {
+  if (!Array.isArray(ids)) {
+    ids = [ids];
+  }
+
+  return {
+    type: STATUS_HIDE,
+    ids,
+  };
+};
+
+export function revealStatus(ids) {
+  if (!Array.isArray(ids)) {
+    ids = [ids];
+  }
+
+  return {
+    type: STATUS_REVEAL,
+    ids,
+  };
+};
diff --git a/app/javascript/mastodon/actions/store.js b/app/javascript/mastodon/actions/store.js
index a1db0fdd5..2dd94a998 100644
--- a/app/javascript/mastodon/actions/store.js
+++ b/app/javascript/mastodon/actions/store.js
@@ -1,4 +1,5 @@
 import { Iterable, fromJS } from 'immutable';
+import { hydrateCompose } from './compose';
 
 export const STORE_HYDRATE = 'STORE_HYDRATE';
 export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
@@ -8,10 +9,14 @@ const convertState = rawState =>
     Iterable.isIndexed(v) ? v.toList() : v.toMap());
 
 export function hydrateStore(rawState) {
-  const state = convertState(rawState);
+  return dispatch => {
+    const state = convertState(rawState);
 
-  return {
-    type: STORE_HYDRATE,
-    state,
+    dispatch({
+      type: STORE_HYDRATE,
+      state,
+    });
+
+    dispatch(hydrateCompose());
   };
 };
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index df6a36379..f0ab16a2d 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -117,13 +117,14 @@ export function refreshTimeline(timelineId, path, params = {}) {
   };
 };
 
-export const refreshHomeTimeline         = () => refreshTimeline('home', '/api/v1/timelines/home');
-export const refreshPublicTimeline       = () => refreshTimeline('public', '/api/v1/timelines/public');
-export const refreshCommunityTimeline    = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
-export const refreshAccountTimeline      = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
-export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
-export const refreshHashtagTimeline      = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
-export const refreshListTimeline         = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
+export const refreshHomeTimeline            = () => refreshTimeline('home', '/api/v1/timelines/home');
+export const refreshPublicTimeline          = () => refreshTimeline('public', '/api/v1/timelines/public');
+export const refreshCommunityTimeline       = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
+export const refreshAccountTimeline         = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
+export const refreshAccountFeaturedTimeline = accountId => refreshTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
+export const refreshAccountMediaTimeline    = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
+export const refreshHashtagTimeline         = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
+export const refreshListTimeline            = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
 
 export function refreshTimelineFail(timeline, error, skipLoading) {
   return {
@@ -161,7 +162,7 @@ export function expandTimeline(timelineId, path, params = {}) {
 export const expandHomeTimeline         = () => expandTimeline('home', '/api/v1/timelines/home');
 export const expandPublicTimeline       = () => expandTimeline('public', '/api/v1/timelines/public');
 export const expandCommunityTimeline    = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
-export const expandAccountTimeline      = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
+export const expandAccountTimeline      = (accountId, withReplies) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
 export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
 export const expandHashtagTimeline      = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
 export const expandListTimeline         = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
diff --git a/app/javascript/mastodon/base_polyfills.js b/app/javascript/mastodon/base_polyfills.js
index 7856b26f9..8fbb17785 100644
--- a/app/javascript/mastodon/base_polyfills.js
+++ b/app/javascript/mastodon/base_polyfills.js
@@ -3,6 +3,7 @@ import 'intl/locale-data/jsonp/en';
 import 'es6-symbol/implement';
 import includes from 'array-includes';
 import assign from 'object-assign';
+import values from 'object.values';
 import isNaN from 'is-nan';
 
 if (!Array.prototype.includes) {
@@ -13,6 +14,10 @@ if (!Object.assign) {
   Object.assign = assign;
 }
 
+if (!Object.values) {
+  values.shim();
+}
+
 if (!Number.isNaN) {
   Number.isNaN = isNaN;
 }
diff --git a/app/javascript/mastodon/components/attachment_list.js b/app/javascript/mastodon/components/attachment_list.js
index 9f2d46ddd..8e5bb0e0b 100644
--- a/app/javascript/mastodon/components/attachment_list.js
+++ b/app/javascript/mastodon/components/attachment_list.js
@@ -1,5 +1,6 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
@@ -8,10 +9,29 @@ export default class AttachmentList extends ImmutablePureComponent {
 
   static propTypes = {
     media: ImmutablePropTypes.list.isRequired,
+    compact: PropTypes.bool,
   };
 
   render () {
-    const { media } = this.props;
+    const { media, compact } = this.props;
+
+    if (compact) {
+      return (
+        <div className='attachment-list compact'>
+          <ul className='attachment-list__list'>
+            {media.map(attachment => {
+              const displayUrl = attachment.get('remote_url') || attachment.get('url');
+
+              return (
+                <li key={attachment.get('id')}>
+                  <a href={displayUrl} target='_blank' rel='noopener'><i className='fa fa-link' /> {filename(displayUrl)}</a>
+                </li>
+              );
+            })}
+          </ul>
+        </div>
+      );
+    }
 
     return (
       <div className='attachment-list'>
@@ -20,11 +40,15 @@ export default class AttachmentList extends ImmutablePureComponent {
         </div>
 
         <ul className='attachment-list__list'>
-          {media.map(attachment => (
-            <li key={attachment.get('id')}>
-              <a href={attachment.get('remote_url')} target='_blank' rel='noopener'>{filename(attachment.get('remote_url'))}</a>
-            </li>
-          ))}
+          {media.map(attachment => {
+            const displayUrl = attachment.get('remote_url') || attachment.get('url');
+
+            return (
+              <li key={attachment.get('id')}>
+                <a href={displayUrl} target='_blank' rel='noopener'>{filename(displayUrl)}</a>
+              </li>
+            );
+          })}
         </ul>
       </div>
     );
diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js
index 6a16e2fc7..34904194f 100644
--- a/app/javascript/mastodon/components/autosuggest_textarea.js
+++ b/app/javascript/mastodon/components/autosuggest_textarea.js
@@ -20,7 +20,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
     word = str.slice(left, right + caretPosition);
   }
 
-  if (!word || word.trim().length < 3 || ['@', ':'].indexOf(word[0]) === -1) {
+  if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
     return [null, null];
   }
 
@@ -170,6 +170,9 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
     if (typeof suggestion === 'object') {
       inner = <AutosuggestEmoji emoji={suggestion} />;
       key   = suggestion.id;
+    } else if (suggestion[0] === '#') {
+      inner = suggestion;
+      key   = suggestion;
     } else {
       inner = <AutosuggestAccountContainer id={suggestion} />;
       key   = suggestion;
diff --git a/app/javascript/mastodon/components/column_back_button_slim.js b/app/javascript/mastodon/components/column_back_button_slim.js
index 3b4f46d99..964c100be 100644
--- a/app/javascript/mastodon/components/column_back_button_slim.js
+++ b/app/javascript/mastodon/components/column_back_button_slim.js
@@ -1,17 +1,8 @@
 import React from 'react';
 import { FormattedMessage } from 'react-intl';
-import PropTypes from 'prop-types';
+import ColumnBackButton from './column_back_button';
 
-export default class ColumnBackButtonSlim extends React.PureComponent {
-
-  static contextTypes = {
-    router: PropTypes.object,
-  };
-
-  handleClick = () => {
-    if (window.history && window.history.length === 1) this.context.router.history.push('/');
-    else this.context.router.history.goBack();
-  }
+export default class ColumnBackButtonSlim extends ColumnBackButton {
 
   render () {
     return (
diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js
index c300db89b..56453aeac 100644
--- a/app/javascript/mastodon/components/column_header.js
+++ b/app/javascript/mastodon/components/column_header.js
@@ -19,10 +19,11 @@ export default class ColumnHeader extends React.PureComponent {
 
   static propTypes = {
     intl: PropTypes.object.isRequired,
-    title: PropTypes.node.isRequired,
-    icon: PropTypes.string.isRequired,
+    title: PropTypes.node,
+    icon: PropTypes.string,
     active: PropTypes.bool,
     multiColumn: PropTypes.bool,
+    extraButton: PropTypes.node,
     showBackButton: PropTypes.bool,
     children: PropTypes.node,
     pinned: PropTypes.bool,
@@ -63,7 +64,7 @@ export default class ColumnHeader extends React.PureComponent {
   }
 
   render () {
-    const { title, icon, active, children, pinned, onPin, multiColumn, showBackButton, intl: { formatMessage } } = this.props;
+    const { title, icon, active, children, pinned, onPin, multiColumn, extraButton, showBackButton, intl: { formatMessage } } = this.props;
     const { collapsed, animating } = this.state;
 
     const wrapperClassName = classNames('column-header__wrapper', {
@@ -125,21 +126,26 @@ export default class ColumnHeader extends React.PureComponent {
     }
 
     if (children || multiColumn) {
-      collapseButton = <button className={collapsibleButtonClassName} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
+      collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
     }
 
+    const hasTitle = icon && title;
+
     return (
       <div className={wrapperClassName}>
         <h1 className={buttonClassName}>
-          <button onClick={this.handleTitleClick}>
-            <i className={`fa fa-fw fa-${icon} column-header__icon`} />
-            <span className='column-header__title'>
+          {hasTitle && (
+            <button onClick={this.handleTitleClick}>
+              <i className={`fa fa-fw fa-${icon} column-header__icon`} />
               {title}
-            </span>
-          </button>
+            </button>
+          )}
+
+          {!hasTitle && backButton}
 
           <div className='column-header__buttons'>
-            {backButton}
+            {hasTitle && backButton}
+            {extraButton}
             {collapseButton}
           </div>
         </h1>
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 43dc0d6e3..c5c6f73b3 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -8,6 +8,7 @@ import spring from 'react-motion/lib/spring';
 import detectPassiveEvents from 'detect-passive-events';
 
 const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
+let id = 0;
 
 class DropdownMenu extends React.PureComponent {
 
@@ -29,6 +30,10 @@ class DropdownMenu extends React.PureComponent {
     placement: 'bottom',
   };
 
+  state = {
+    mounted: false,
+  };
+
   handleDocumentClick = e => {
     if (this.node && !this.node.contains(e.target)) {
       this.props.onClose();
@@ -38,6 +43,7 @@ class DropdownMenu extends React.PureComponent {
   componentDidMount () {
     document.addEventListener('click', this.handleDocumentClick, false);
     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
+    this.setState({ mounted: true });
   }
 
   componentWillUnmount () {
@@ -82,11 +88,15 @@ class DropdownMenu extends React.PureComponent {
 
   render () {
     const { items, style, placement, arrowOffsetLeft, arrowOffsetTop } = this.props;
+    const { mounted } = this.state;
 
     return (
       <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
         {({ opacity, scaleX, scaleY }) => (
-          <div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }} ref={this.setRef}>
+          // It should not be transformed when mounting because the resulting
+          // size will be used to determine the coordinate of the menu by
+          // react-overlays
+          <div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
             <div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
 
             <ul>
@@ -115,8 +125,10 @@ export default class Dropdown extends React.PureComponent {
     status: ImmutablePropTypes.map,
     isUserTouching: PropTypes.func,
     isModalOpen: PropTypes.bool.isRequired,
-    onModalOpen: PropTypes.func,
-    onModalClose: PropTypes.func,
+    onOpen: PropTypes.func.isRequired,
+    onClose: PropTypes.func.isRequired,
+    dropdownPlacement: PropTypes.string,
+    openDropdownId: PropTypes.number,
   };
 
   static defaultProps = {
@@ -124,37 +136,28 @@ export default class Dropdown extends React.PureComponent {
   };
 
   state = {
-    expanded: false,
+    id: id++,
   };
 
-  handleClick = () => {
-    if (!this.state.expanded && this.props.isUserTouching() && this.props.onModalOpen) {
-      const { status, items } = this.props;
-
-      this.props.onModalOpen({
-        status,
-        actions: items,
-        onClick: this.handleItemClick,
-      });
+  handleClick = ({ target }) => {
+    if (this.state.id === this.props.openDropdownId) {
+      this.handleClose();
+    } else {
+      const { top } = target.getBoundingClientRect();
+      const placement = top * 2 < innerHeight ? 'bottom' : 'top';
 
-      return;
+      this.props.onOpen(this.state.id, this.handleItemClick, placement);
     }
-
-    this.setState({ expanded: !this.state.expanded });
   }
 
   handleClose = () => {
-    if (this.props.onModalClose) {
-      this.props.onModalClose();
-    }
-
-    this.setState({ expanded: false });
+    this.props.onClose(this.state.id);
   }
 
   handleKeyDown = e => {
     switch(e.key) {
     case 'Enter':
-      this.handleClick();
+      this.handleClick(e);
       break;
     case 'Escape':
       this.handleClose();
@@ -186,22 +189,22 @@ export default class Dropdown extends React.PureComponent {
   }
 
   render () {
-    const { icon, items, size, title, disabled } = this.props;
-    const { expanded } = this.state;
+    const { icon, items, size, title, disabled, dropdownPlacement, openDropdownId } = this.props;
+    const open = this.state.id === openDropdownId;
 
     return (
       <div onKeyDown={this.handleKeyDown}>
         <IconButton
           icon={icon}
           title={title}
-          active={expanded}
+          active={open}
           disabled={disabled}
           size={size}
           ref={this.setTargetRef}
           onClick={this.handleClick}
         />
 
-        <Overlay show={expanded} placement='bottom' target={this.findTarget}>
+        <Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
           <DropdownMenu items={items} onClose={this.handleClose} />
         </Overlay>
       </div>
diff --git a/app/javascript/mastodon/components/extended_video_player.js b/app/javascript/mastodon/components/extended_video_player.js
index f8bd067e8..9e2f6835a 100644
--- a/app/javascript/mastodon/components/extended_video_player.js
+++ b/app/javascript/mastodon/components/extended_video_player.js
@@ -11,6 +11,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
     time: PropTypes.number,
     controls: PropTypes.bool.isRequired,
     muted: PropTypes.bool.isRequired,
+    onClick: PropTypes.func,
   };
 
   handleLoadedData = () => {
@@ -31,6 +32,12 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
     this.video = c;
   }
 
+  handleClick = e => {
+    e.stopPropagation();
+    const handler = this.props.onClick;
+    if (handler) handler();
+  }
+
   render () {
     const { src, muted, controls, alt } = this.props;
 
@@ -46,6 +53,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
           muted={muted}
           controls={controls}
           loop={!controls}
+          onClick={this.handleClick}
         />
       </div>
     );
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 20febdb16..9310e7c96 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -6,12 +6,32 @@ import IconButton from './icon_button';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { isIOS } from '../is_mobile';
 import classNames from 'classnames';
-import { autoPlayGif } from '../initial_state';
+import { autoPlayGif, displaySensitiveMedia } from '../initial_state';
 
 const messages = defineMessages({
   toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
 });
 
+const shiftToPoint = (containerToImageRatio, containerSize, imageSize, focusSize, toMinus) => {
+  const containerCenter = Math.floor(containerSize / 2);
+  const focusFactor     = (focusSize + 1) / 2;
+  const scaledImage     = Math.floor(imageSize / containerToImageRatio);
+
+  let focus = Math.floor(focusFactor * scaledImage);
+
+  if (toMinus) focus = scaledImage - focus;
+
+  let focusOffset = focus - containerCenter;
+
+  const remainder = scaledImage - focus;
+  const containerRemainder = containerSize - containerCenter;
+
+  if (remainder < containerRemainder) focusOffset -= containerRemainder - remainder;
+  if (focusOffset < 0) focusOffset = 0;
+
+  return (focusOffset * -100 / containerSize) + '%';
+};
+
 class Item extends React.PureComponent {
 
   static contextTypes = {
@@ -24,6 +44,8 @@ class Item extends React.PureComponent {
     index: PropTypes.number.isRequired,
     size: PropTypes.number.isRequired,
     onClick: PropTypes.func.isRequired,
+    containerWidth: PropTypes.number,
+    containerHeight: PropTypes.number,
   };
 
   static defaultProps = {
@@ -62,7 +84,7 @@ class Item extends React.PureComponent {
   }
 
   render () {
-    const { attachment, index, size, standalone } = this.props;
+    const { attachment, index, size, standalone, containerWidth, containerHeight } = this.props;
 
     let width  = 50;
     let height = 100;
@@ -116,16 +138,50 @@ class Item extends React.PureComponent {
     let thumbnail = '';
 
     if (attachment.get('type') === 'image') {
-      const previewUrl = attachment.get('preview_url');
+      const previewUrl   = attachment.get('preview_url');
       const previewWidth = attachment.getIn(['meta', 'small', 'width']);
 
-      const originalUrl = attachment.get('url');
-      const originalWidth = attachment.getIn(['meta', 'original', 'width']);
+      const originalUrl    = attachment.get('url');
+      const originalWidth  = attachment.getIn(['meta', 'original', 'width']);
+      const originalHeight = attachment.getIn(['meta', 'original', 'height']);
 
       const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
 
       const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
-      const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
+      const sizes  = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
+
+      const focusX     = attachment.getIn(['meta', 'focus', 'x']);
+      const focusY     = attachment.getIn(['meta', 'focus', 'y']);
+      const imageStyle = {};
+
+      if (originalWidth && originalHeight && containerWidth && containerHeight && focusX && focusY) {
+        const widthRatio  = originalWidth / (containerWidth * (width / 100));
+        const heightRatio = originalHeight / (containerHeight * (height / 100));
+
+        let hShift = 0;
+        let vShift = 0;
+
+        if (widthRatio > heightRatio) {
+          hShift = shiftToPoint(heightRatio, (containerWidth * (width / 100)), originalWidth, focusX);
+        } else if(widthRatio < heightRatio) {
+          vShift = shiftToPoint(widthRatio, (containerHeight * (height / 100)), originalHeight, focusY, true);
+        }
+
+        if (originalWidth > originalHeight) {
+          imageStyle.height   = '100%';
+          imageStyle.width    = 'auto';
+          imageStyle.minWidth = '100%';
+        } else {
+          imageStyle.height    = 'auto';
+          imageStyle.width     = '100%';
+          imageStyle.minHeight = '100%';
+        }
+
+        imageStyle.top  = vShift;
+        imageStyle.left = hShift;
+      } else {
+        imageStyle.height = '100%';
+      }
 
       thumbnail = (
         <a
@@ -134,7 +190,14 @@ class Item extends React.PureComponent {
           onClick={this.handleClick}
           target='_blank'
         >
-          <img src={previewUrl} srcSet={srcSet} sizes={sizes} alt={attachment.get('description')} title={attachment.get('description')} />
+          <img
+            src={previewUrl}
+            srcSet={srcSet}
+            sizes={sizes}
+            alt={attachment.get('description')}
+            title={attachment.get('description')}
+            style={imageStyle}
+          />
         </a>
       );
     } else if (attachment.get('type') === 'gifv') {
@@ -187,7 +250,7 @@ export default class MediaGallery extends React.PureComponent {
   };
 
   state = {
-    visible: !this.props.sensitive,
+    visible: !this.props.sensitive || displaySensitiveMedia,
   };
 
   componentWillReceiveProps (nextProps) {
@@ -205,7 +268,7 @@ export default class MediaGallery extends React.PureComponent {
   }
 
   handleRef = (node) => {
-    if (node && this.isStandaloneEligible()) {
+    if (node /*&& this.isStandaloneEligible()*/) {
       // offsetWidth triggers a layout, so only calculate when we need to
       this.setState({
         width: node.offsetWidth,
@@ -227,15 +290,12 @@ export default class MediaGallery extends React.PureComponent {
     const style = {};
 
     if (this.isStandaloneEligible()) {
-      if (!visible && width) {
-        // only need to forcibly set the height in "sensitive" mode
+      if (width) {
         style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
-      } else {
-        // layout automatically, using image's natural aspect ratio
-        style.height = '';
       }
+    } else if (width) {
+      style.height = width / (16/9);
     } else {
-      // crop the image
       style.height = height;
     }
 
@@ -249,7 +309,7 @@ export default class MediaGallery extends React.PureComponent {
       }
 
       children = (
-        <button className='media-spoiler' onClick={this.handleOpen} style={style} ref={this.handleRef}>
+        <button type='button' className='media-spoiler' onClick={this.handleOpen} style={style} ref={this.handleRef}>
           <span className='media-spoiler__warning'>{warning}</span>
           <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
         </button>
@@ -260,12 +320,12 @@ export default class MediaGallery extends React.PureComponent {
       if (this.isStandaloneEligible()) {
         children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
       } else {
-        children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
+        children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} containerWidth={width} containerHeight={style.height} />);
       }
     }
 
     return (
-      <div className='media-gallery' style={style}>
+      <div className='media-gallery' style={style} ref={this.handleRef}>
         <div className={classNames('spoiler-button', { 'spoiler-button--visible': visible })}>
           <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
         </div>
diff --git a/app/javascript/mastodon/components/permalink.js b/app/javascript/mastodon/components/permalink.js
index d726d37a2..b369e9812 100644
--- a/app/javascript/mastodon/components/permalink.js
+++ b/app/javascript/mastodon/components/permalink.js
@@ -12,9 +12,15 @@ export default class Permalink extends React.PureComponent {
     href: PropTypes.string.isRequired,
     to: PropTypes.string.isRequired,
     children: PropTypes.node,
+    onInterceptClick: PropTypes.func,
   };
 
-  handleClick = (e) => {
+  handleClick = e => {
+    if (this.props.onInterceptClick && this.props.onInterceptClick()) {
+      e.preventDefault();
+      return;
+    }
+
     if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
       e.preventDefault();
       this.context.router.history.push(this.props.to);
@@ -22,7 +28,7 @@ export default class Permalink extends React.PureComponent {
   }
 
   render () {
-    const { href, children, className, ...other } = this.props;
+    const { href, children, className, onInterceptClick, ...other } = this.props;
 
     return (
       <a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}>
diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js
index 71228ca6c..ac3e404df 100644
--- a/app/javascript/mastodon/components/scrollable_list.js
+++ b/app/javascript/mastodon/components/scrollable_list.js
@@ -17,7 +17,7 @@ export default class ScrollableList extends PureComponent {
 
   static propTypes = {
     scrollKey: PropTypes.string.isRequired,
-    onScrollToBottom: PropTypes.func,
+    onLoadMore: PropTypes.func.isRequired,
     onScrollToTop: PropTypes.func,
     onScroll: PropTypes.func,
     trackScroll: PropTypes.bool,
@@ -45,9 +45,11 @@ export default class ScrollableList extends PureComponent {
       const offset = scrollHeight - scrollTop - clientHeight;
       this._oldScrollPosition = scrollHeight - scrollTop;
 
-      if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
-        this.props.onScrollToBottom();
-      } else if (scrollTop < 100 && this.props.onScrollToTop) {
+      if (400 > offset && this.props.onLoadMore && !this.props.isLoading) {
+        this.props.onLoadMore();
+      }
+
+      if (scrollTop < 100 && this.props.onScrollToTop) {
         this.props.onScrollToTop();
       } else if (this.props.onScroll) {
         this.props.onScroll();
@@ -138,7 +140,7 @@ export default class ScrollableList extends PureComponent {
 
   handleLoadMore = (e) => {
     e.preventDefault();
-    this.props.onScrollToBottom();
+    this.props.onLoadMore();
   }
 
   _recentlyMoved () {
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index c030510a0..a918a94f8 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -7,6 +7,7 @@ import RelativeTimestamp from './relative_timestamp';
 import DisplayName from './display_name';
 import StatusContent from './status_content';
 import StatusActionBar from './status_action_bar';
+import AttachmentList from './attachment_list';
 import { FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { MediaGallery, Video } from '../features/ui/util/async-components';
@@ -36,16 +37,13 @@ export default class Status extends ImmutablePureComponent {
     onBlock: PropTypes.func,
     onEmbed: PropTypes.func,
     onHeightChange: PropTypes.func,
+    onToggleHidden: PropTypes.func,
     muted: PropTypes.bool,
     hidden: PropTypes.bool,
     onMoveUp: PropTypes.func,
     onMoveDown: PropTypes.func,
   };
 
-  state = {
-    isExpanded: false,
-  }
-
   // Avoid checking props that are functions (and whose equality will always
   // evaluate to false. See react-immutable-pure-component for usage.
   updateOnProps = [
@@ -55,8 +53,6 @@ export default class Status extends ImmutablePureComponent {
     'hidden',
   ]
 
-  updateOnStates = ['isExpanded']
-
   handleClick = () => {
     if (!this.context.router) {
       return;
@@ -75,7 +71,7 @@ export default class Status extends ImmutablePureComponent {
   }
 
   handleExpandedToggle = () => {
-    this.setState({ isExpanded: !this.state.isExpanded });
+    this.props.onToggleHidden(this._properStatus());
   };
 
   renderLoadingMediaGallery () {
@@ -138,8 +134,7 @@ export default class Status extends ImmutablePureComponent {
     let media = null;
     let statusAvatar, prepend;
 
-    const { hidden }     = this.props;
-    const { isExpanded } = this.state;
+    const { hidden, featured } = this.props;
 
     let { status, account, ...other } = this.props;
 
@@ -156,7 +151,14 @@ export default class Status extends ImmutablePureComponent {
       );
     }
 
-    if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
+    if (featured) {
+      prepend = (
+        <div className='status__prepend'>
+          <div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-thumb-tack status__prepend-icon' /></div>
+          <FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
+        </div>
+      );
+    } else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
       const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
 
       prepend = (
@@ -170,9 +172,14 @@ export default class Status extends ImmutablePureComponent {
       status  = status.get('reblog');
     }
 
-    if (status.get('media_attachments').size > 0 && !this.props.muted) {
-      if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
-
+    if (status.get('media_attachments').size > 0) {
+      if (this.props.muted || status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
+        media = (
+          <AttachmentList
+            compact
+            media={status.get('media_attachments')}
+          />
+        );
       } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
         const video = status.getIn(['media_attachments', 0]);
 
@@ -184,6 +191,7 @@ export default class Status extends ImmutablePureComponent {
                 src={video.get('url')}
                 width={239}
                 height={110}
+                inline
                 sensitive={status.get('sensitive')}
                 onOpenVideo={this.handleOpenVideo}
               />
@@ -234,7 +242,7 @@ export default class Status extends ImmutablePureComponent {
               </a>
             </div>
 
-            <StatusContent status={status} onClick={this.handleClick} expanded={isExpanded} onExpandedToggle={this.handleExpandedToggle} />
+            <StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
 
             {media}
 
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 3b8155632..701b5702c 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -24,7 +24,12 @@ export default class StatusContent extends React.PureComponent {
   };
 
   _updateStatusLinks () {
-    const node  = this.node;
+    const node = this.node;
+
+    if (!node) {
+      return;
+    }
+
     const links = node.querySelectorAll('a');
 
     for (var i = 0; i < links.length; ++i) {
@@ -115,6 +120,10 @@ export default class StatusContent extends React.PureComponent {
   render () {
     const { status } = this.props;
 
+    if (status.get('content').length === 0) {
+      return null;
+    }
+
     const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
 
     const content = { __html: status.get('contentHtml') };
diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js
index 5acaf714e..3bebf702c 100644
--- a/app/javascript/mastodon/components/status_list.js
+++ b/app/javascript/mastodon/components/status_list.js
@@ -11,7 +11,8 @@ export default class StatusList extends ImmutablePureComponent {
   static propTypes = {
     scrollKey: PropTypes.string.isRequired,
     statusIds: ImmutablePropTypes.list.isRequired,
-    onScrollToBottom: PropTypes.func,
+    featuredStatusIds: ImmutablePropTypes.list,
+    onLoadMore: PropTypes.func,
     onScrollToTop: PropTypes.func,
     onScroll: PropTypes.func,
     trackScroll: PropTypes.bool,
@@ -50,7 +51,7 @@ export default class StatusList extends ImmutablePureComponent {
   }
 
   render () {
-    const { statusIds, ...other }  = this.props;
+    const { statusIds, featuredStatusIds, ...other }  = this.props;
     const { isLoading, isPartial } = other;
 
     if (isPartial) {
@@ -68,8 +69,8 @@ export default class StatusList extends ImmutablePureComponent {
       );
     }
 
-    const scrollableContent = (isLoading || statusIds.size > 0) ? (
-      statusIds.map((statusId) => (
+    let scrollableContent = (isLoading || statusIds.size > 0) ? (
+      statusIds.map(statusId => (
         <StatusContainer
           key={statusId}
           id={statusId}
@@ -79,6 +80,18 @@ export default class StatusList extends ImmutablePureComponent {
       ))
     ) : null;
 
+    if (scrollableContent && featuredStatusIds) {
+      scrollableContent = featuredStatusIds.map(statusId => (
+        <StatusContainer
+          key={`f-${statusId}`}
+          id={statusId}
+          featured
+          onMoveUp={this.handleMoveUp}
+          onMoveDown={this.handleMoveDown}
+        />
+      )).concat(scrollableContent);
+    }
+
     return (
       <ScrollableList {...other} ref={this.setRef}>
         {scrollableContent}
diff --git a/app/javascript/mastodon/containers/dropdown_menu_container.js b/app/javascript/mastodon/containers/dropdown_menu_container.js
index 151f25390..7cbcdcd35 100644
--- a/app/javascript/mastodon/containers/dropdown_menu_container.js
+++ b/app/javascript/mastodon/containers/dropdown_menu_container.js
@@ -1,3 +1,4 @@
+import { openDropdownMenu, closeDropdownMenu } from '../actions/dropdown_menu';
 import { openModal, closeModal } from '../actions/modal';
 import { connect } from 'react-redux';
 import DropdownMenu from '../components/dropdown_menu';
@@ -5,12 +6,22 @@ import { isUserTouching } from '../is_mobile';
 
 const mapStateToProps = state => ({
   isModalOpen: state.get('modal').modalType === 'ACTIONS',
+  dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
+  openDropdownId: state.getIn(['dropdown_menu', 'openId']),
 });
 
-const mapDispatchToProps = dispatch => ({
-  isUserTouching,
-  onModalOpen: props => dispatch(openModal('ACTIONS', props)),
-  onModalClose: () => dispatch(closeModal()),
+const mapDispatchToProps = (dispatch, { status, items }) => ({
+  onOpen(id, onItemClick, dropdownPlacement) {
+    dispatch(isUserTouching() ? openModal('ACTIONS', {
+      status,
+      actions: items,
+      onClick: onItemClick,
+    }) : openDropdownMenu(id, dropdownPlacement));
+  },
+  onClose(id) {
+    dispatch(closeModal());
+    dispatch(closeDropdownMenu(id));
+  },
 });
 
 export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);
diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js
index b22540204..8ba1015b5 100644
--- a/app/javascript/mastodon/containers/status_container.js
+++ b/app/javascript/mastodon/containers/status_container.js
@@ -15,7 +15,13 @@ import {
   unpin,
 } from '../actions/interactions';
 import { blockAccount } from '../actions/accounts';
-import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
+import {
+  muteStatus,
+  unmuteStatus,
+  deleteStatus,
+  hideStatus,
+  revealStatus,
+} from '../actions/statuses';
 import { initMuteModal } from '../actions/mutes';
 import { initReport } from '../actions/reports';
 import { openModal } from '../actions/modal';
@@ -128,6 +134,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     }
   },
 
+  onToggleHidden (status) {
+    if (status.get('hidden')) {
+      dispatch(revealStatus(status.get('id')));
+    } else {
+      dispatch(hideStatus(status.get('id')));
+    }
+  },
+
 });
 
 export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
diff --git a/app/javascript/mastodon/containers/timeline_container.js b/app/javascript/mastodon/containers/timeline_container.js
index e84c921ee..8719bb5c9 100644
--- a/app/javascript/mastodon/containers/timeline_container.js
+++ b/app/javascript/mastodon/containers/timeline_container.js
@@ -6,6 +6,7 @@ import { hydrateStore } from '../actions/store';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from '../locales';
 import PublicTimeline from '../features/standalone/public_timeline';
+import CommunityTimeline from '../features/standalone/community_timeline';
 import HashtagTimeline from '../features/standalone/hashtag_timeline';
 import initialState from '../initial_state';
 
@@ -23,17 +24,24 @@ export default class TimelineContainer extends React.PureComponent {
   static propTypes = {
     locale: PropTypes.string.isRequired,
     hashtag: PropTypes.string,
+    showPublicTimeline: PropTypes.bool.isRequired,
+  };
+
+  static defaultProps = {
+    showPublicTimeline: initialState.settings.known_fediverse,
   };
 
   render () {
-    const { locale, hashtag } = this.props;
+    const { locale, hashtag, showPublicTimeline } = this.props;
 
     let timeline;
 
     if (hashtag) {
       timeline = <HashtagTimeline hashtag={hashtag} />;
-    } else {
+    } else if (showPublicTimeline) {
       timeline = <PublicTimeline />;
+    } else {
+      timeline = <CommunityTimeline />;
     }
 
     return (
diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js
index cb849fa5d..b538fa5fc 100644
--- a/app/javascript/mastodon/features/account/components/action_bar.js
+++ b/app/javascript/mastodon/features/account/components/action_bar.js
@@ -53,11 +53,11 @@ export default class ActionBar extends React.PureComponent {
     let extraInfo = '';
 
     menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
+
     if ('share' in navigator) {
       menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
     }
-    menu.push(null);
-    menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
+
     menu.push(null);
 
     if (account.get('id') === me) {
@@ -122,7 +122,7 @@ export default class ActionBar extends React.PureComponent {
 
           <div className='account__action-bar-links'>
             <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}>
-              <span><FormattedMessage id='account.posts' defaultMessage='Posts' /></span>
+              <span><FormattedMessage id='account.posts' defaultMessage='Toots' /></span>
               <strong><FormattedNumber value={account.get('statuses_count')} /></strong>
             </Link>
 
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index b8605d11f..bb7b3b632 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -13,6 +13,7 @@ const messages = defineMessages({
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
   follow: { id: 'account.follow', defaultMessage: 'Follow' },
   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
+  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
 });
 
 class Avatar extends ImmutablePureComponent {
@@ -69,6 +70,7 @@ export default class Header extends ImmutablePureComponent {
   static propTypes = {
     account: ImmutablePropTypes.map,
     onFollow: PropTypes.func.isRequired,
+    onBlock: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
@@ -80,11 +82,20 @@ export default class Header extends ImmutablePureComponent {
     }
 
     let info        = '';
+    let mutingInfo  = '';
     let actionBtn   = '';
     let lockedIcon  = '';
 
     if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
       info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
+    } else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
+      info = <span className='account--follows-info'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>;
+    }
+
+    if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
+      mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>;
+    } else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
+      mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>;
     }
 
     if (me !== account.get('id')) {
@@ -100,6 +111,12 @@ export default class Header extends ImmutablePureComponent {
             <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
           </div>
         );
+      } else if (account.getIn(['relationship', 'blocking'])) {
+        actionBtn = (
+          <div className='account--action-button'>
+            <IconButton size={26} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
+          </div>
+        );
       }
     }
 
@@ -124,6 +141,7 @@ export default class Header extends ImmutablePureComponent {
           <div className='account__header__content' dangerouslySetInnerHTML={content} />
 
           {info}
+          {mutingInfo}
           {actionBtn}
         </div>
       </div>
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js
index dda3d4e37..f7a802dc7 100644
--- a/app/javascript/mastodon/features/account_gallery/components/media_item.js
+++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js
@@ -2,6 +2,7 @@ import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Permalink from '../../../components/permalink';
+import { displaySensitiveMedia } from '../../../initial_state';
 
 export default class MediaItem extends ImmutablePureComponent {
 
@@ -9,28 +10,51 @@ export default class MediaItem extends ImmutablePureComponent {
     media: ImmutablePropTypes.map.isRequired,
   };
 
+  state = {
+    visible: !this.props.media.getIn(['status', 'sensitive']) || displaySensitiveMedia,
+  };
+
+  handleClick = () => {
+    if (!this.state.visible) {
+      this.setState({ visible: true });
+      return true;
+    }
+
+    return false;
+  }
+
   render () {
     const { media } = this.props;
+    const { visible } = this.state;
     const status = media.get('status');
+    const focusX = media.getIn(['meta', 'focus', 'x']);
+    const focusY = media.getIn(['meta', 'focus', 'y']);
+    const x = ((focusX /  2) + .5) * 100;
+    const y = ((focusY / -2) + .5) * 100;
+    const style = {};
 
-    let content, style;
+    let label, icon;
 
     if (media.get('type') === 'gifv') {
-      content = <span className='media-gallery__gifv__label'>GIF</span>;
+      label = <span className='media-gallery__gifv__label'>GIF</span>;
     }
 
-    if (!status.get('sensitive')) {
-      style = { backgroundImage: `url(${media.get('preview_url')})` };
+    if (visible) {
+      style.backgroundImage    = `url(${media.get('preview_url')})`;
+      style.backgroundPosition = `${x}% ${y}%`;
+    } else {
+      icon = (
+        <span className='account-gallery__item__icons'>
+          <i className='fa fa-eye-slash' />
+        </span>
+      );
     }
 
     return (
       <div className='account-gallery__item'>
-        <Permalink
-          to={`/statuses/${status.get('id')}`}
-          href={status.get('url')}
-          style={style}
-        >
-          {content}
+        <Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style} onInterceptClick={this.handleClick}>
+          {icon}
+          {label}
         </Permalink>
       </div>
     );
diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js
index ece219a3d..4b408256a 100644
--- a/app/javascript/mastodon/features/account_gallery/index.js
+++ b/app/javascript/mastodon/features/account_gallery/index.js
@@ -11,7 +11,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import { getAccountGallery } from '../../selectors';
 import MediaItem from './components/media_item';
 import HeaderContainer from '../account_timeline/containers/header_container';
-import { FormattedMessage } from 'react-intl';
 import { ScrollContainer } from 'react-router-scroll-4';
 import LoadMore from '../../components/load_more';
 
@@ -89,10 +88,6 @@ export default class AccountGallery extends ImmutablePureComponent {
           <div className='scrollable' onScroll={this.handleScroll}>
             <HeaderContainer accountId={this.props.params.accountId} />
 
-            <div className='account-section-headline'>
-              <FormattedMessage id='account.media' defaultMessage='Media' />
-            </div>
-
             <div className='account-gallery__container'>
               {medias.map(media => (
                 <MediaItem
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index 0ddb6b6c1..9d594fb0c 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -6,6 +6,8 @@ import ActionBar from '../../account/components/action_bar';
 import MissingIndicator from '../../../components/missing_indicator';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import MovedNote from './moved_note';
+import { FormattedMessage } from 'react-intl';
+import { NavLink } from 'react-router-dom';
 
 export default class Header extends ImmutablePureComponent {
 
@@ -19,6 +21,7 @@ export default class Header extends ImmutablePureComponent {
     onMute: PropTypes.func.isRequired,
     onBlockDomain: PropTypes.func.isRequired,
     onUnblockDomain: PropTypes.func.isRequired,
+    hideTabs: PropTypes.bool,
   };
 
   static contextTypes = {
@@ -66,7 +69,7 @@ export default class Header extends ImmutablePureComponent {
   }
 
   render () {
-    const { account } = this.props;
+    const { account, hideTabs } = this.props;
 
     if (account === null) {
       return <MissingIndicator />;
@@ -79,6 +82,7 @@ export default class Header extends ImmutablePureComponent {
         <InnerHeader
           account={account}
           onFollow={this.handleFollow}
+          onBlock={this.handleBlock}
         />
 
         <ActionBar
@@ -91,6 +95,14 @@ export default class Header extends ImmutablePureComponent {
           onBlockDomain={this.handleBlockDomain}
           onUnblockDomain={this.handleUnblockDomain}
         />
+
+        {!hideTabs && (
+          <div className='account__section-headline'>
+            <NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
+            <NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink>
+            <NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
+          </div>
+        )}
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index f8c85c296..f5f2475ea 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import { fetchAccount } from '../../actions/accounts';
-import { refreshAccountTimeline, expandAccountTimeline } from '../../actions/timelines';
+import { refreshAccountTimeline, refreshAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
 import StatusList from '../../components/status_list';
 import LoadingIndicator from '../../components/loading_indicator';
 import Column from '../ui/components/column';
@@ -12,11 +12,16 @@ import ColumnBackButton from '../../components/column_back_button';
 import { List as ImmutableList } from 'immutable';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
-const mapStateToProps = (state, props) => ({
-  statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()),
-  isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']),
-  hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']),
-});
+const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
+  const path = withReplies ? `${accountId}:with_replies` : accountId;
+
+  return {
+    statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
+    featuredStatusIds: state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()),
+    isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
+    hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']),
+  };
+};
 
 @connect(mapStateToProps)
 export default class AccountTimeline extends ImmutablePureComponent {
@@ -25,30 +30,36 @@ export default class AccountTimeline extends ImmutablePureComponent {
     params: PropTypes.object.isRequired,
     dispatch: PropTypes.func.isRequired,
     statusIds: ImmutablePropTypes.list,
+    featuredStatusIds: ImmutablePropTypes.list,
     isLoading: PropTypes.bool,
     hasMore: PropTypes.bool,
+    withReplies: PropTypes.bool,
   };
 
   componentWillMount () {
-    this.props.dispatch(fetchAccount(this.props.params.accountId));
-    this.props.dispatch(refreshAccountTimeline(this.props.params.accountId));
+    const { params: { accountId }, withReplies } = this.props;
+
+    this.props.dispatch(fetchAccount(accountId));
+    this.props.dispatch(refreshAccountFeaturedTimeline(accountId));
+    this.props.dispatch(refreshAccountTimeline(accountId, withReplies));
   }
 
   componentWillReceiveProps (nextProps) {
-    if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
+    if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
       this.props.dispatch(fetchAccount(nextProps.params.accountId));
-      this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId));
+      this.props.dispatch(refreshAccountFeaturedTimeline(nextProps.params.accountId));
+      this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies));
     }
   }
 
-  handleScrollToBottom = () => {
+  handleLoadMore = () => {
     if (!this.props.isLoading && this.props.hasMore) {
-      this.props.dispatch(expandAccountTimeline(this.props.params.accountId));
+      this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies));
     }
   }
 
   render () {
-    const { statusIds, isLoading, hasMore } = this.props;
+    const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
 
     if (!statusIds && isLoading) {
       return (
@@ -66,9 +77,10 @@ export default class AccountTimeline extends ImmutablePureComponent {
           prepend={<HeaderContainer accountId={this.props.params.accountId} />}
           scrollKey='account_timeline'
           statusIds={statusIds}
+          featuredStatusIds={featuredStatusIds}
           isLoading={isLoading}
           hasMore={hasMore}
-          onScrollToBottom={this.handleScrollToBottom}
+          onLoadMore={this.handleLoadMore}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index a876c5197..663ccfb8e 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -50,6 +50,7 @@ export default class ComposeForm extends ImmutablePureComponent {
     onPaste: PropTypes.func.isRequired,
     onPickEmoji: PropTypes.func.isRequired,
     showSearch: PropTypes.bool,
+    anyMedia: PropTypes.bool,
   };
 
   static defaultProps = {
@@ -142,10 +143,10 @@ export default class ComposeForm extends ImmutablePureComponent {
   }
 
   render () {
-    const { intl, onPaste, showSearch } = this.props;
+    const { intl, onPaste, showSearch, anyMedia } = this.props;
     const disabled = this.props.is_submitting;
     const text     = [this.props.spoiler_text, countableText(this.props.text)].join('');
-
+    const disabledButton = disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
     let publishText = '';
 
     if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
@@ -203,7 +204,7 @@ export default class ComposeForm extends ImmutablePureComponent {
         </div>
 
         <div className='compose-form__publish'>
-          <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div>
+          <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabledButton} block /></div>
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js
index 398fc44ce..71c0a203f 100644
--- a/app/javascript/mastodon/features/compose/components/search.js
+++ b/app/javascript/mastodon/features/compose/components/search.js
@@ -4,6 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import Overlay from 'react-overlays/lib/Overlay';
 import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
+import { searchEnabled } from '../../../initial_state';
 
 const messages = defineMessages({
   placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
@@ -17,7 +18,7 @@ class SearchPopout extends React.PureComponent {
 
   render () {
     const { style } = this.props;
-
+    const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' /> : <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />;
     return (
       <div style={{ ...style, position: 'absolute', width: 285 }}>
         <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
@@ -32,7 +33,7 @@ class SearchPopout extends React.PureComponent {
                 <li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
               </ul>
 
-              <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />
+              {extraInformation}
             </div>
           )}
         </Motion>
diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js
index d16f7fce7..84455563c 100644
--- a/app/javascript/mastodon/features/compose/components/search_results.js
+++ b/app/javascript/mastodon/features/compose/components/search_results.js
@@ -22,6 +22,8 @@ export default class SearchResults extends ImmutablePureComponent {
       count   += results.get('accounts').size;
       accounts = (
         <div className='search-results__section'>
+          <h5><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
+
           {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
         </div>
       );
@@ -31,6 +33,8 @@ export default class SearchResults extends ImmutablePureComponent {
       count   += results.get('statuses').size;
       statuses = (
         <div className='search-results__section'>
+          <h5><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
+
           {results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
         </div>
       );
@@ -40,6 +44,8 @@ export default class SearchResults extends ImmutablePureComponent {
       count += results.get('hashtags').size;
       hashtags = (
         <div className='search-results__section'>
+          <h5><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
+
           {results.get('hashtags').map(hashtag => (
             <Link key={hashtag} className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}>
               #{hashtag}
diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js
index 3a3d17710..61b2d19e0 100644
--- a/app/javascript/mastodon/features/compose/components/upload.js
+++ b/app/javascript/mastodon/features/compose/components/upload.js
@@ -1,15 +1,13 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
-import IconButton from '../../../components/icon_button';
 import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import classNames from 'classnames';
 
 const messages = defineMessages({
-  undo: { id: 'upload_form.undo', defaultMessage: 'Undo' },
   description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
 });
 
@@ -21,6 +19,7 @@ export default class Upload extends ImmutablePureComponent {
     intl: PropTypes.object.isRequired,
     onUndo: PropTypes.func.isRequired,
     onDescriptionChange: PropTypes.func.isRequired,
+    onOpenFocalPoint: PropTypes.func.isRequired,
   };
 
   state = {
@@ -33,6 +32,10 @@ export default class Upload extends ImmutablePureComponent {
     this.props.onUndo(this.props.media.get('id'));
   }
 
+  handleFocalPointClick = () => {
+    this.props.onOpenFocalPoint(this.props.media.get('id'));
+  }
+
   handleInputChange = e => {
     this.setState({ dirtyDescription: e.target.value });
   }
@@ -63,13 +66,20 @@ export default class Upload extends ImmutablePureComponent {
     const { intl, media } = this.props;
     const active          = this.state.hovered || this.state.focused;
     const description     = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || '';
+    const focusX = media.getIn(['meta', 'focus', 'x']);
+    const focusY = media.getIn(['meta', 'focus', 'y']);
+    const x = ((focusX /  2) + .5) * 100;
+    const y = ((focusY / -2) + .5) * 100;
 
     return (
       <div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
         <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
           {({ scale }) => (
-            <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})` }}>
-              <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.handleUndoClick} />
+            <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
+              <div className={classNames('compose-form__upload__actions', { active })}>
+                <button className='icon-button' onClick={this.handleUndoClick}><i className='fa fa-times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Undo' /></button>
+                {media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><i className='fa fa-crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>}
+              </div>
 
               <div className={classNames('compose-form__upload-description', { active })}>
                 <label>
diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
index 5f5509dbe..ede23d361 100644
--- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js
+++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
@@ -23,6 +23,7 @@ const mapStateToProps = state => ({
   is_submitting: state.getIn(['compose', 'is_submitting']),
   is_uploading: state.getIn(['compose', 'is_uploading']),
   showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
+  anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
 });
 
 const mapDispatchToProps = (dispatch) => ({
diff --git a/app/javascript/mastodon/features/compose/containers/upload_container.js b/app/javascript/mastodon/features/compose/containers/upload_container.js
index ca9c3b704..d6b57e5ff 100644
--- a/app/javascript/mastodon/features/compose/containers/upload_container.js
+++ b/app/javascript/mastodon/features/compose/containers/upload_container.js
@@ -1,6 +1,7 @@
 import { connect } from 'react-redux';
 import Upload from '../components/upload';
 import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose';
+import { openModal } from '../../../actions/modal';
 
 const mapStateToProps = (state, { id }) => ({
   media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
@@ -13,7 +14,11 @@ const mapDispatchToProps = dispatch => ({
   },
 
   onDescriptionChange: (id, description) => {
-    dispatch(changeUploadCompose(id, description));
+    dispatch(changeUploadCompose(id, { description }));
+  },
+
+  onOpenFocalPoint: id => {
+    dispatch(openModal('FOCAL_POINT', { id }));
   },
 
 });
diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.js b/app/javascript/mastodon/features/compose/containers/warning_container.js
index b9f280958..dbb80dfb0 100644
--- a/app/javascript/mastodon/features/compose/containers/warning_container.js
+++ b/app/javascript/mastodon/features/compose/containers/warning_container.js
@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
 import { FormattedMessage } from 'react-intl';
 import { me } from '../../../initial_state';
 
-const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i;
+const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z]\w*)/i;
 
 const mapStateToProps = state => ({
   needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index 84e3a2338..138bc4e2e 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -12,6 +12,7 @@ import Motion from '../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import SearchResultsContainer from './containers/search_results_container';
 import { changeComposing } from '../../actions/compose';
+import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
 
 const messages = defineMessages({
   start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -94,7 +95,11 @@ export default class Compose extends React.PureComponent {
           <div className='drawer__inner' onFocus={this.onFocus}>
             <NavigationContainer onClose={this.onBlur} />
             <ComposeFormContainer />
-            {multiColumn && <div className='mastodon' />}
+            {multiColumn && (
+              <div className='drawer__inner__mastodon'>
+                <img alt='' src={elephantUIPlane} />
+              </div>
+            )}
           </div>
 
           <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js
index 67b107bc8..6f1c863b4 100644
--- a/app/javascript/mastodon/features/favourited_statuses/index.js
+++ b/app/javascript/mastodon/features/favourited_statuses/index.js
@@ -62,7 +62,7 @@ export default class Favourites extends ImmutablePureComponent {
     this.column = c;
   }
 
-  handleScrollToBottom = debounce(() => {
+  handleLoadMore = debounce(() => {
     this.props.dispatch(expandFavouritedStatuses());
   }, 300, { leading: true })
 
@@ -89,7 +89,7 @@ export default class Favourites extends ImmutablePureComponent {
           scrollKey={`favourited_statuses-${columnId}`}
           hasMore={hasMore}
           isLoading={isLoading}
-          onScrollToBottom={this.handleScrollToBottom}
+          onLoadMore={this.handleLoadMore}
         />
       </Column>
     );
diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js
index f64ed7948..919a89332 100644
--- a/app/javascript/mastodon/features/followers/index.js
+++ b/app/javascript/mastodon/features/followers/index.js
@@ -80,7 +80,7 @@ export default class Followers extends ImmutablePureComponent {
         <ScrollContainer scrollKey='followers'>
           <div className='scrollable' onScroll={this.handleScroll}>
             <div className='followers'>
-              <HeaderContainer accountId={this.props.params.accountId} />
+              <HeaderContainer accountId={this.props.params.accountId} hideTabs />
               {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
               {loadMore}
             </div>
diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js
index a0c0fac05..5719259d1 100644
--- a/app/javascript/mastodon/features/following/index.js
+++ b/app/javascript/mastodon/features/following/index.js
@@ -80,7 +80,7 @@ export default class Following extends ImmutablePureComponent {
         <ScrollContainer scrollKey='following'>
           <div className='scrollable' onScroll={this.handleScroll}>
             <div className='following'>
-              <HeaderContainer accountId={this.props.params.accountId} />
+              <HeaderContainer accountId={this.props.params.accountId} hideTabs />
               {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
               {loadMore}
             </div>
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index ee789e180..3a875169e 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -8,6 +8,8 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { me } from '../../initial_state';
+import { fetchFollowRequests } from '../../actions/accounts';
+import { List as ImmutableList } from 'immutable';
 
 const messages = defineMessages({
   heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -32,9 +34,25 @@ const messages = defineMessages({
 const mapStateToProps = state => ({
   myAccount: state.getIn(['accounts', me]),
   columns: state.getIn(['settings', 'columns']),
+  unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
+  unreadNotifications: state.getIn(['notifications', 'unread']),
 });
 
-@connect(mapStateToProps)
+const mapDispatchToProps = dispatch => ({
+  fetchFollowRequests: () => dispatch(fetchFollowRequests()),
+});
+
+const badgeDisplay = (number, limit) => {
+  if (number === 0) {
+    return undefined;
+  } else if (limit && number >= limit) {
+    return `${limit}+`;
+  } else {
+    return number;
+  }
+};
+
+@connect(mapStateToProps, mapDispatchToProps)
 @injectIntl
 export default class GettingStarted extends ImmutablePureComponent {
 
@@ -43,10 +61,21 @@ export default class GettingStarted extends ImmutablePureComponent {
     myAccount: ImmutablePropTypes.map.isRequired,
     columns: ImmutablePropTypes.list,
     multiColumn: PropTypes.bool,
+    fetchFollowRequests: PropTypes.func.isRequired,
+    unreadFollowRequests: PropTypes.number,
+    unreadNotifications: PropTypes.number,
   };
 
+  componentDidMount () {
+    const { myAccount, fetchFollowRequests } = this.props;
+
+    if (myAccount.get('locked')) {
+      fetchFollowRequests();
+    }
+  }
+
   render () {
-    const { intl, myAccount, columns, multiColumn } = this.props;
+    const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications } = this.props;
 
     const navItems = [];
 
@@ -56,7 +85,7 @@ export default class GettingStarted extends ImmutablePureComponent {
       }
 
       if (!columns.find(item => item.get('id') === 'NOTIFICATIONS')) {
-        navItems.push(<ColumnLink key='1' icon='bell' text={intl.formatMessage(messages.notifications)} to='/notifications' />);
+        navItems.push(<ColumnLink key='1' icon='bell' text={intl.formatMessage(messages.notifications)} badge={badgeDisplay(unreadNotifications)} to='/notifications' />);
       }
 
       if (!columns.find(item => item.get('id') === 'COMMUNITY')) {
@@ -74,7 +103,7 @@ export default class GettingStarted extends ImmutablePureComponent {
     );
 
     if (myAccount.get('locked')) {
-      navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
+      navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
     }
 
     if (multiColumn) {
diff --git a/app/javascript/mastodon/features/notifications/components/clear_column_button.js b/app/javascript/mastodon/features/notifications/components/clear_column_button.js
index 22a10753f..e0bf4c82d 100644
--- a/app/javascript/mastodon/features/notifications/components/clear_column_button.js
+++ b/app/javascript/mastodon/features/notifications/components/clear_column_button.js
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { FormattedMessage } from 'react-intl';
 
-export default class ClearColumnButton extends React.Component {
+export default class ClearColumnButton extends React.PureComponent {
 
   static propTypes = {
     onClick: PropTypes.func.isRequired,
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js
index 35b430bfb..cb9d025ea 100644
--- a/app/javascript/mastodon/features/notifications/index.js
+++ b/app/javascript/mastodon/features/notifications/index.js
@@ -50,8 +50,14 @@ export default class Notifications extends React.PureComponent {
     trackScroll: true,
   };
 
-  handleScrollToBottom = debounce(() => {
+  componentWillUnmount () {
+    this.handleLoadMore.cancel();
+    this.handleScrollToTop.cancel();
+    this.handleScroll.cancel();
     this.props.dispatch(scrollTopNotifications(false));
+  }
+
+  handleLoadMore = debounce(() => {
     this.props.dispatch(expandNotifications());
   }, 300, { leading: true });
 
@@ -136,7 +142,7 @@ export default class Notifications extends React.PureComponent {
         isLoading={isLoading}
         hasMore={hasMore}
         emptyMessage={emptyMessage}
-        onScrollToBottom={this.handleScrollToBottom}
+        onLoadMore={this.handleLoadMore}
         onScrollToTop={this.handleScrollToTop}
         onScroll={this.handleScroll}
         shouldUpdateScroll={shouldUpdateScroll}
diff --git a/app/javascript/mastodon/features/report/components/status_check_box.js b/app/javascript/mastodon/features/report/components/status_check_box.js
index cc9232201..9ff75a082 100644
--- a/app/javascript/mastodon/features/report/components/status_check_box.js
+++ b/app/javascript/mastodon/features/report/components/status_check_box.js
@@ -2,6 +2,10 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import Toggle from 'react-toggle';
+import noop from 'lodash/noop';
+import StatusContent from '../../../components/status_content';
+import { MediaGallery, Video } from '../../ui/util/async-components';
+import Bundle from '../../ui/components/bundle';
 
 export default class StatusCheckBox extends React.PureComponent {
 
@@ -14,18 +18,48 @@ export default class StatusCheckBox extends React.PureComponent {
 
   render () {
     const { status, checked, onToggle, disabled } = this.props;
-    const content = { __html: status.get('contentHtml') };
+    let media = null;
 
     if (status.get('reblog')) {
       return null;
     }
 
+    if (status.get('media_attachments').size > 0) {
+      if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
+
+      } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
+        const video = status.getIn(['media_attachments', 0]);
+
+        media = (
+          <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
+            {Component => (
+              <Component
+                preview={video.get('preview_url')}
+                src={video.get('url')}
+                width={239}
+                height={110}
+                inline
+                sensitive={status.get('sensitive')}
+                onOpenVideo={noop}
+              />
+            )}
+          </Bundle>
+        );
+      } else {
+        media = (
+          <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
+            {Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={noop} />}
+          </Bundle>
+        );
+      }
+    }
+
     return (
       <div className='status-check-box'>
-        <div
-          className='status__content'
-          dangerouslySetInnerHTML={content}
-        />
+        <div className='status-check-box__status'>
+          <StatusContent status={status} />
+          {media}
+        </div>
 
         <div className='status-check-box-toggle'>
           <Toggle checked={checked} onChange={onToggle} disabled={disabled} />
diff --git a/app/javascript/mastodon/features/standalone/community_timeline/index.js b/app/javascript/mastodon/features/standalone/community_timeline/index.js
new file mode 100644
index 000000000..51e50e1f5
--- /dev/null
+++ b/app/javascript/mastodon/features/standalone/community_timeline/index.js
@@ -0,0 +1,74 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import StatusListContainer from '../../ui/containers/status_list_container';
+import {
+  refreshCommunityTimeline,
+  expandCommunityTimeline,
+} from '../../../actions/timelines';
+import Column from '../../../components/column';
+import ColumnHeader from '../../../components/column_header';
+import { defineMessages, injectIntl } from 'react-intl';
+import { connectCommunityStream } from '../../../actions/streaming';
+
+const messages = defineMessages({
+  title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
+});
+
+@connect()
+@injectIntl
+export default class CommunityTimeline extends React.PureComponent {
+
+  static propTypes = {
+    dispatch: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
+  };
+
+  handleHeaderClick = () => {
+    this.column.scrollTop();
+  }
+
+  setRef = c => {
+    this.column = c;
+  }
+
+  componentDidMount () {
+    const { dispatch } = this.props;
+
+    dispatch(refreshCommunityTimeline());
+    this.disconnect = dispatch(connectCommunityStream());
+  }
+
+  componentWillUnmount () {
+    if (this.disconnect) {
+      this.disconnect();
+      this.disconnect = null;
+    }
+  }
+
+  handleLoadMore = () => {
+    this.props.dispatch(expandCommunityTimeline());
+  }
+
+  render () {
+    const { intl } = this.props;
+
+    return (
+      <Column ref={this.setRef}>
+        <ColumnHeader
+          icon='users'
+          title={intl.formatMessage(messages.title)}
+          onClick={this.handleHeaderClick}
+        />
+
+        <StatusListContainer
+          timelineId='community'
+          loadMore={this.handleLoadMore}
+          scrollKey='standalone_public_timeline'
+          trackScroll={false}
+        />
+      </Column>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js
index 2f6a7831e..b52f3c4fa 100644
--- a/app/javascript/mastodon/features/status/components/card.js
+++ b/app/javascript/mastodon/features/status/components/card.js
@@ -20,6 +20,39 @@ const getHostname = url => {
   return parser.hostname;
 };
 
+const trim = (text, len) => {
+  const cut = text.indexOf(' ', len);
+
+  if (cut === -1) {
+    return text;
+  }
+
+  return text.substring(0, cut) + (text.length > len ? '…' : '');
+};
+
+const domParser = new DOMParser();
+
+const addAutoPlay = html => {
+  const document = domParser.parseFromString(html, 'text/html').documentElement;
+  const iframe = document.querySelector('iframe');
+
+  if (iframe) {
+    if (iframe.src.indexOf('?') !== -1) {
+      iframe.src += '&';
+    } else {
+      iframe.src += '?';
+    }
+
+    iframe.src += 'autoplay=1&auto_play=1';
+
+    // DOM parser creates html/body elements around original HTML fragment,
+    // so we need to get innerHTML out of the body and not the entire document
+    return document.querySelector('body').innerHTML;
+  }
+
+  return html;
+};
+
 export default class Card extends React.PureComponent {
 
   static propTypes = {
@@ -33,9 +66,16 @@ export default class Card extends React.PureComponent {
   };
 
   state = {
-    width: 0,
+    width: 280,
+    embedded: false,
   };
 
+  componentWillReceiveProps (nextProps) {
+    if (this.props.card !== nextProps.card) {
+      this.setState({ embedded: false });
+    }
+  }
+
   handlePhotoClick = () => {
     const { card, onOpenMedia } = this.props;
 
@@ -57,56 +97,14 @@ export default class Card extends React.PureComponent {
     );
   };
 
-  renderLink () {
-    const { card, maxDescription } = this.props;
-    const { width }  = this.state;
-    const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width);
-
-    let image    = '';
-    let provider = card.get('provider_name');
-
-    if (card.get('image')) {
-      image = (
-        <div className='status-card__image'>
-          <img src={card.get('image')} alt={card.get('title')} className='status-card__image-image' width={card.get('width')} height={card.get('height')} />
-        </div>
-      );
-    }
-
-    if (provider.length < 1) {
-      provider = decodeIDNA(getHostname(card.get('url')));
-    }
-
-    const className = classnames('status-card', { horizontal });
-
-    return (
-      <a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}>
-        {image}
-
-        <div className='status-card__content'>
-          <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>
-          {!horizontal && <p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p>}
-          <span className='status-card__host'>{provider}</span>
-        </div>
-      </a>
-    );
-  }
-
-  renderPhoto () {
+  handleEmbedClick = () => {
     const { card } = this.props;
 
-    return (
-      <img
-        className='status-card-photo'
-        onClick={this.handlePhotoClick}
-        role='button'
-        tabIndex='0'
-        src={card.get('embed_url')}
-        alt={card.get('title')}
-        width={card.get('width')}
-        height={card.get('height')}
-      />
-    );
+    if (card.get('type') === 'photo') {
+      this.handlePhotoClick();
+    } else {
+      this.setState({ embedded: true });
+    }
   }
 
   setRef = c => {
@@ -117,7 +115,7 @@ export default class Card extends React.PureComponent {
 
   renderVideo () {
     const { card }  = this.props;
-    const content   = { __html: card.get('html') };
+    const content   = { __html: addAutoPlay(card.get('html')) };
     const { width } = this.state;
     const ratio     = card.get('width') / card.get('height');
     const height    = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio);
@@ -125,7 +123,7 @@ export default class Card extends React.PureComponent {
     return (
       <div
         ref={this.setRef}
-        className='status-card-video'
+        className='status-card__image status-card-video'
         dangerouslySetInnerHTML={content}
         style={{ height }}
       />
@@ -133,23 +131,76 @@ export default class Card extends React.PureComponent {
   }
 
   render () {
-    const { card } = this.props;
+    const { card, maxDescription } = this.props;
+    const { width, embedded }      = this.state;
 
     if (card === null) {
       return null;
     }
 
-    switch(card.get('type')) {
-    case 'link':
-      return this.renderLink();
-    case 'photo':
-      return this.renderPhoto();
-    case 'video':
-      return this.renderVideo();
-    case 'rich':
-    default:
-      return null;
+    const provider    = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
+    const horizontal  = card.get('width') > card.get('height') && (card.get('width') + 100 >= width) || card.get('type') !== 'link';
+    const className   = classnames('status-card', { horizontal });
+    const interactive = card.get('type') !== 'link';
+    const title       = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
+    const ratio       = card.get('width') / card.get('height');
+    const height      = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio);
+
+    const description = (
+      <div className='status-card__content'>
+        {title}
+        {!horizontal && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
+        <span className='status-card__host'>{provider}</span>
+      </div>
+    );
+
+    let embed     = '';
+    let thumbnail = <div style={{ backgroundImage: `url(${card.get('image')})`, width: horizontal ? width : null, height: horizontal ? height : null }} className='status-card__image-image' />;
+
+    if (interactive) {
+      if (embedded) {
+        embed = this.renderVideo();
+      } else {
+        let iconVariant = 'play';
+
+        if (card.get('type') === 'photo') {
+          iconVariant = 'search-plus';
+        }
+
+        embed = (
+          <div className='status-card__image'>
+            {thumbnail}
+
+            <div className='status-card__actions'>
+              <div>
+                <button onClick={this.handleEmbedClick}><i className={`fa fa-${iconVariant}`} /></button>
+                <a href={card.get('url')} target='_blank' rel='noopener'><i className='fa fa-external-link' /></a>
+              </div>
+            </div>
+          </div>
+        );
+      }
+
+      return (
+        <div className={className} ref={this.setRef}>
+          {embed}
+          {description}
+        </div>
+      );
+    } else if (card.get('image')) {
+      embed = (
+        <div className='status-card__image'>
+          {thumbnail}
+        </div>
+      );
     }
+
+    return (
+      <a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}>
+        {embed}
+        {description}
+      </a>
+    );
   }
 
 }
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index abdb9a3f6..b5f516032 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -22,6 +22,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
     status: ImmutablePropTypes.map.isRequired,
     onOpenMedia: PropTypes.func.isRequired,
     onOpenVideo: PropTypes.func.isRequired,
+    onToggleHidden: PropTypes.func.isRequired,
   };
 
   handleAccountClick = (e) => {
@@ -37,6 +38,10 @@ export default class DetailedStatus extends ImmutablePureComponent {
     this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
   }
 
+  handleExpandedToggle = () => {
+    this.props.onToggleHidden(this.props.status);
+  }
+
   render () {
     const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
 
@@ -57,6 +62,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
             src={video.get('url')}
             width={300}
             height={150}
+            inline
             onOpenVideo={this.handleOpenVideo}
             sensitive={status.get('sensitive')}
           />
@@ -104,7 +110,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
           <DisplayName account={status.get('account')} />
         </a>
 
-        <StatusContent status={status} />
+        <StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
 
         {media}
 
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index 73ea9321d..2f482b292 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -21,12 +21,19 @@ import {
   mentionCompose,
 } from '../../actions/compose';
 import { blockAccount } from '../../actions/accounts';
-import { muteStatus, unmuteStatus, deleteStatus } from '../../actions/statuses';
+import {
+  muteStatus,
+  unmuteStatus,
+  deleteStatus,
+  hideStatus,
+  revealStatus,
+} from '../../actions/statuses';
 import { initMuteModal } from '../../actions/mutes';
 import { initReport } from '../../actions/reports';
 import { makeGetStatus } from '../../selectors';
 import { ScrollContainer } from 'react-router-scroll-4';
 import ColumnBackButton from '../../components/column_back_button';
+import ColumnHeader from '../../components/column_header';
 import StatusContainer from '../../containers/status_container';
 import { openModal } from '../../actions/modal';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
@@ -39,6 +46,8 @@ const messages = defineMessages({
   deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
   deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
   blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
+  revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
+  hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
 });
 
 const makeMapStateToProps = () => {
@@ -163,6 +172,25 @@ export default class Status extends ImmutablePureComponent {
     }
   }
 
+  handleToggleHidden = (status) => {
+    if (status.get('hidden')) {
+      this.props.dispatch(revealStatus(status.get('id')));
+    } else {
+      this.props.dispatch(hideStatus(status.get('id')));
+    }
+  }
+
+  handleToggleAll = () => {
+    const { status, ancestorsIds, descendantsIds } = this.props;
+    const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS());
+
+    if (status.get('hidden')) {
+      this.props.dispatch(revealStatus(statusIds));
+    } else {
+      this.props.dispatch(hideStatus(statusIds));
+    }
+  }
+
   handleBlockClick = (account) => {
     const { dispatch, intl } = this.props;
 
@@ -293,7 +321,7 @@ export default class Status extends ImmutablePureComponent {
 
   render () {
     let ancestors, descendants;
-    const { status, ancestorsIds, descendantsIds } = this.props;
+    const { status, ancestorsIds, descendantsIds, intl } = this.props;
     const { fullscreen } = this.state;
 
     if (status === null) {
@@ -325,7 +353,12 @@ export default class Status extends ImmutablePureComponent {
 
     return (
       <Column>
-        <ColumnBackButton />
+        <ColumnHeader
+          showBackButton
+          extraButton={(
+            <button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><i className={`fa fa-${status.get('hidden') ? 'eye-slash' : 'eye'}`} /></button>
+          )}
+        />
 
         <ScrollContainer scrollKey='thread'>
           <div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}>
@@ -337,6 +370,7 @@ export default class Status extends ImmutablePureComponent {
                   status={status}
                   onOpenVideo={this.handleOpenVideo}
                   onOpenMedia={this.handleOpenMedia}
+                  onToggleHidden={this.handleToggleHidden}
                 />
 
                 <ActionBar
diff --git a/app/javascript/mastodon/features/ui/components/__tests__/column-test.js b/app/javascript/mastodon/features/ui/components/__tests__/column-test.js
index 1e5e1d8dc..11cc1b6e8 100644
--- a/app/javascript/mastodon/features/ui/components/__tests__/column-test.js
+++ b/app/javascript/mastodon/features/ui/components/__tests__/column-test.js
@@ -21,13 +21,13 @@ describe('<Column />', () => {
           <div className='scrollable' />
         </Column>
       );
-      wrapper.find(ColumnHeader).simulate('click');
+      wrapper.find(ColumnHeader).find('button').simulate('click');
       expect(global.requestAnimationFrame.mock.calls.length).toEqual(1);
     });
 
     it('does not try to scroll if there is no scrollable content', () => {
       const wrapper = mount(<Column heading='notifications' />);
-      wrapper.find(ColumnHeader).simulate('click');
+      wrapper.find(ColumnHeader).find('button').simulate('click');
       expect(global.requestAnimationFrame.mock.calls.length).toEqual(0);
     });
   });
diff --git a/app/javascript/mastodon/features/ui/components/bundle.js b/app/javascript/mastodon/features/ui/components/bundle.js
index fc88e0c70..e7d935251 100644
--- a/app/javascript/mastodon/features/ui/components/bundle.js
+++ b/app/javascript/mastodon/features/ui/components/bundle.js
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 const emptyComponent = () => null;
 const noop = () => { };
 
-class Bundle extends React.Component {
+class Bundle extends React.PureComponent {
 
   static propTypes = {
     fetchComponent: PropTypes.func.isRequired,
@@ -26,7 +26,7 @@ class Bundle extends React.Component {
     onFetchFail: noop,
   }
 
-  static cache = {}
+  static cache = new Map
 
   state = {
     mod: undefined,
@@ -51,13 +51,12 @@ class Bundle extends React.Component {
 
   load = (props) => {
     const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
+    const cachedMod = Bundle.cache.get(fetchComponent);
 
     onFetch();
 
-    if (Bundle.cache[fetchComponent.name]) {
-      const mod = Bundle.cache[fetchComponent.name];
-
-      this.setState({ mod: mod.default });
+    if (cachedMod) {
+      this.setState({ mod: cachedMod.default });
       onFetchSuccess();
       return Promise.resolve();
     }
@@ -71,7 +70,7 @@ class Bundle extends React.Component {
 
     return fetchComponent()
       .then((mod) => {
-        Bundle.cache[fetchComponent.name] = mod;
+        Bundle.cache.set(fetchComponent, mod);
         this.setState({ mod: mod.default });
         onFetchSuccess();
       })
diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error.js b/app/javascript/mastodon/features/ui/components/bundle_column_error.js
index cd124746a..f39ebd900 100644
--- a/app/javascript/mastodon/features/ui/components/bundle_column_error.js
+++ b/app/javascript/mastodon/features/ui/components/bundle_column_error.js
@@ -13,7 +13,7 @@ const messages = defineMessages({
   retry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' },
 });
 
-class BundleColumnError extends React.Component {
+class BundleColumnError extends React.PureComponent {
 
   static propTypes = {
     onRetry: PropTypes.func.isRequired,
diff --git a/app/javascript/mastodon/features/ui/components/bundle_modal_error.js b/app/javascript/mastodon/features/ui/components/bundle_modal_error.js
index 928bfe1f7..f9365b95b 100644
--- a/app/javascript/mastodon/features/ui/components/bundle_modal_error.js
+++ b/app/javascript/mastodon/features/ui/components/bundle_modal_error.js
@@ -10,7 +10,7 @@ const messages = defineMessages({
   close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' },
 });
 
-class BundleModalError extends React.Component {
+class BundleModalError extends React.PureComponent {
 
   static propTypes = {
     onRetry: PropTypes.func.isRequired,
diff --git a/app/javascript/mastodon/features/ui/components/column_header.js b/app/javascript/mastodon/features/ui/components/column_header.js
index fdf9aab1a..e8bdd8054 100644
--- a/app/javascript/mastodon/features/ui/components/column_header.js
+++ b/app/javascript/mastodon/features/ui/components/column_header.js
@@ -1,5 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 
 export default class ColumnHeader extends React.PureComponent {
 
@@ -16,19 +17,20 @@ export default class ColumnHeader extends React.PureComponent {
   }
 
   render () {
-    const { type, active, columnHeaderId } = this.props;
+    const { icon, type, active, columnHeaderId } = this.props;
+    let iconElement = '';
 
-    let icon = '';
-
-    if (this.props.icon) {
-      icon = <i className={`fa fa-fw fa-${this.props.icon} column-header__icon`} />;
+    if (icon) {
+      iconElement = <i className={`fa fa-fw fa-${icon} column-header__icon`} />;
     }
 
     return (
-      <div role='button heading' tabIndex='0' className={`column-header ${active ? 'active' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}>
-        {icon}
-        {type}
-      </div>
+      <h1 className={classNames('column-header', { active })} id={columnHeaderId || null}>
+        <button onClick={this.handleClick}>
+          {iconElement}
+          {type}
+        </button>
+      </h1>
     );
   }
 
diff --git a/app/javascript/mastodon/features/ui/components/column_link.js b/app/javascript/mastodon/features/ui/components/column_link.js
index a90616213..25c2d1cf8 100644
--- a/app/javascript/mastodon/features/ui/components/column_link.js
+++ b/app/javascript/mastodon/features/ui/components/column_link.js
@@ -2,12 +2,15 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Link } from 'react-router-dom';
 
-const ColumnLink = ({ icon, text, to, href, method }) => {
+const ColumnLink = ({ icon, text, to, href, method, badge }) => {
+  const badgeElement = typeof badge !== 'undefined' ? <span className='column-link__badge'>{badge}</span> : null;
+
   if (href) {
     return (
       <a href={href} className='column-link' data-method={method}>
         <i className={`fa fa-fw fa-${icon} column-link__icon`} />
         {text}
+        {badgeElement}
       </a>
     );
   } else {
@@ -15,6 +18,7 @@ const ColumnLink = ({ icon, text, to, href, method }) => {
       <Link to={to} className='column-link'>
         <i className={`fa fa-fw fa-${icon} column-link__icon`} />
         {text}
+        {badgeElement}
       </Link>
     );
   }
@@ -26,6 +30,7 @@ ColumnLink.propTypes = {
   to: PropTypes.string,
   href: PropTypes.string,
   method: PropTypes.string,
+  badge: PropTypes.node,
 };
 
 export default ColumnLink;
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js
index a01e5a390..e82c46402 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.js
+++ b/app/javascript/mastodon/features/ui/components/columns_area.js
@@ -6,6 +6,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 
 import ReactSwipeableViews from 'react-swipeable-views';
 import { links, getIndex, getLink } from './tabs_bar';
+import { Link } from 'react-router-dom';
 
 import BundleContainer from '../containers/bundle_container';
 import ColumnLoading from './column_loading';
@@ -152,11 +153,19 @@ export default class ColumnsArea extends ImmutablePureComponent {
     this.pendingIndex = null;
 
     if (singleColumn) {
-      return columnIndex !== -1 ? (
-        <ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
+      const floatingActionButton = this.context.router.history.location.pathname === '/statuses/new' ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button'><i className='fa fa-pencil' /></Link>;
+
+      return columnIndex !== -1 ? [
+        <ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
           {links.map(this.renderView)}
-        </ReactSwipeableViews>
-      ) : <div className='columns-area'>{children}</div>;
+        </ReactSwipeableViews>,
+
+        floatingActionButton,
+      ] : [
+        <div className='columns-area'>{children}</div>,
+
+        floatingActionButton,
+      ];
     }
 
     return (
diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.js b/app/javascript/mastodon/features/ui/components/focal_point_modal.js
new file mode 100644
index 000000000..1038e1864
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.js
@@ -0,0 +1,122 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+import ImageLoader from './image_loader';
+import classNames from 'classnames';
+import { changeUploadCompose } from '../../../actions/compose';
+import { getPointerPosition } from '../../video';
+
+const mapStateToProps = (state, { id }) => ({
+  media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
+});
+
+const mapDispatchToProps = (dispatch, { id }) => ({
+
+  onSave: (x, y) => {
+    dispatch(changeUploadCompose(id, { focus: `${x.toFixed(2)},${y.toFixed(2)}` }));
+  },
+
+});
+
+@connect(mapStateToProps, mapDispatchToProps)
+export default class FocalPointModal extends ImmutablePureComponent {
+
+  static propTypes = {
+    media: ImmutablePropTypes.map.isRequired,
+  };
+
+  state = {
+    x: 0,
+    y: 0,
+    focusX: 0,
+    focusY: 0,
+    dragging: false,
+  };
+
+  componentWillMount () {
+    this.updatePositionFromMedia(this.props.media);
+  }
+
+  componentWillReceiveProps (nextProps) {
+    if (this.props.media.get('id') !== nextProps.media.get('id')) {
+      this.updatePositionFromMedia(nextProps.media);
+    }
+  }
+
+  componentWillUnmount () {
+    document.removeEventListener('mousemove', this.handleMouseMove);
+    document.removeEventListener('mouseup', this.handleMouseUp);
+  }
+
+  handleMouseDown = e => {
+    document.addEventListener('mousemove', this.handleMouseMove);
+    document.addEventListener('mouseup', this.handleMouseUp);
+
+    this.updatePosition(e);
+    this.setState({ dragging: true });
+  }
+
+  handleMouseMove = e => {
+    this.updatePosition(e);
+  }
+
+  handleMouseUp = () => {
+    document.removeEventListener('mousemove', this.handleMouseMove);
+    document.removeEventListener('mouseup', this.handleMouseUp);
+
+    this.setState({ dragging: false });
+    this.props.onSave(this.state.focusX, this.state.focusY);
+  }
+
+  updatePosition = e => {
+    const { x, y } = getPointerPosition(this.node, e);
+    const focusX   = (x - .5) *  2;
+    const focusY   = (y - .5) * -2;
+
+    this.setState({ x, y, focusX, focusY });
+  }
+
+  updatePositionFromMedia = media => {
+    const focusX = media.getIn(['meta', 'focus', 'x']);
+    const focusY = media.getIn(['meta', 'focus', 'y']);
+
+    if (focusX && focusY) {
+      const x = (focusX /  2) + .5;
+      const y = (focusY / -2) + .5;
+
+      this.setState({ x, y, focusX, focusY });
+    } else {
+      this.setState({ x: 0.5, y: 0.5, focusX: 0, focusY: 0 });
+    }
+  }
+
+  setRef = c => {
+    this.node = c;
+  }
+
+  render () {
+    const { media } = this.props;
+    const { x, y, dragging } = this.state;
+
+    const width  = media.getIn(['meta', 'original', 'width']) || null;
+    const height = media.getIn(['meta', 'original', 'height']) || null;
+
+    return (
+      <div className='modal-root__modal video-modal'>
+        <div className={classNames('focal-point', { dragging })} ref={this.setRef}>
+          <ImageLoader
+            previewSrc={media.get('preview_url')}
+            src={media.get('url')}
+            width={width}
+            height={height}
+          />
+
+          <div className='focal-point__reticle' style={{ top: `${y * 100}%`, left: `${x * 100}%` }} />
+          <div className='focal-point__overlay' onMouseDown={this.handleMouseDown} />
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js
index e3e7197c5..c7360a726 100644
--- a/app/javascript/mastodon/features/ui/components/image_loader.js
+++ b/app/javascript/mastodon/features/ui/components/image_loader.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import classNames from 'classnames';
+import ZoomableImage from './zoomable_image';
 
 export default class ImageLoader extends React.PureComponent {
 
@@ -10,6 +11,7 @@ export default class ImageLoader extends React.PureComponent {
     previewSrc: PropTypes.string,
     width: PropTypes.number,
     height: PropTypes.number,
+    onClick: PropTypes.func,
   }
 
   static defaultProps = {
@@ -24,6 +26,7 @@ export default class ImageLoader extends React.PureComponent {
   }
 
   removers = [];
+  canvas = null;
 
   get canvasContext() {
     if (!this.canvas) {
@@ -43,6 +46,10 @@ export default class ImageLoader extends React.PureComponent {
     }
   }
 
+  componentWillUnmount () {
+    this.removeEventListeners();
+  }
+
   loadImage (props) {
     this.removeEventListeners();
     this.setState({ loading: true, error: false });
@@ -118,7 +125,7 @@ export default class ImageLoader extends React.PureComponent {
   }
 
   render () {
-    const { alt, src, width, height } = this.props;
+    const { alt, src, width, height, onClick } = this.props;
     const { loading } = this.state;
 
     const className = classNames('image-loader', {
@@ -128,22 +135,19 @@ export default class ImageLoader extends React.PureComponent {
 
     return (
       <div className={className}>
-        <canvas
-          className='image-loader__preview-canvas'
-          width={width}
-          height={height}
-          ref={this.setCanvasRef}
-          style={{ opacity: loading ? 1 : 0 }}
-        />
-
-        {!loading && (
-          <img
-            alt={alt}
-            className='image-loader__img'
-            src={src}
+        {loading ? (
+          <canvas
+            className='image-loader__preview-canvas'
+            ref={this.setCanvasRef}
             width={width}
             height={height}
           />
+        ) : (
+          <ZoomableImage
+            alt={alt}
+            src={src}
+            onClick={onClick}
+          />
         )}
       </div>
     );
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index 02591a51f..72ef32256 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import ExtendedVideoPlayer from '../../../components/extended_video_player';
+import classNames from 'classnames';
 import { defineMessages, injectIntl } from 'react-intl';
 import IconButton from '../../../components/icon_button';
 import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -26,6 +27,7 @@ export default class MediaModal extends ImmutablePureComponent {
 
   state = {
     index: null,
+    navigationHidden: false,
   };
 
   handleSwipe = (index) => {
@@ -68,14 +70,21 @@ export default class MediaModal extends ImmutablePureComponent {
     return this.state.index !== null ? this.state.index : this.props.index;
   }
 
+  toggleNavigation = () => {
+    this.setState(prevState => ({
+      navigationHidden: !prevState.navigationHidden,
+    }));
+  };
+
   render () {
     const { media, intl, onClose } = this.props;
+    const { navigationHidden } = this.state;
 
     const index = this.getIndex();
     let pagination = [];
 
-    const leftNav  = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
-    const rightNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav  modal-container__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
+    const leftNav  = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
+    const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav  media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
 
     if (media.size > 1) {
       pagination = media.map((item, i) => {
@@ -92,9 +101,30 @@ export default class MediaModal extends ImmutablePureComponent {
       const height = image.getIn(['meta', 'original', 'height']) || null;
 
       if (image.get('type') === 'image') {
-        return <ImageLoader previewSrc={image.get('preview_url')} src={image.get('url')} width={width} height={height} alt={image.get('description')} key={image.get('url')} />;
+        return (
+          <ImageLoader
+            previewSrc={image.get('preview_url')}
+            src={image.get('url')}
+            width={width}
+            height={height}
+            alt={image.get('description')}
+            key={image.get('url')}
+            onClick={this.toggleNavigation}
+          />
+        );
       } else if (image.get('type') === 'gifv') {
-        return <ExtendedVideoPlayer src={image.get('url')} muted controls={false} width={width} height={height} key={image.get('preview_url')} alt={image.get('description')} />;
+        return (
+          <ExtendedVideoPlayer
+            src={image.get('url')}
+            muted
+            controls={false}
+            width={width}
+            height={height}
+            key={image.get('preview_url')}
+            alt={image.get('description')}
+            onClick={this.toggleNavigation}
+          />
+        );
       }
 
       return null;
@@ -104,21 +134,43 @@ export default class MediaModal extends ImmutablePureComponent {
       alignItems: 'center', // center vertically
     };
 
+    const navigationClassName = classNames('media-modal__navigation', {
+      'media-modal__navigation--hidden': navigationHidden,
+    });
+
     return (
       <div className='modal-root__modal media-modal'>
-        {leftNav}
-
-        <div className='media-modal__content'>
-          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
-          <ReactSwipeableViews containerStyle={containerStyle} onChangeIndex={this.handleSwipe} index={index}>
-            {content}
-          </ReactSwipeableViews>
+        <div
+          className='media-modal__closer'
+          role='presentation'
+          onClick={onClose}
+        >
+          <div className='media-modal__content'>
+            <ReactSwipeableViews
+              style={{
+                // you can't use 100vh, because the viewport height is taller
+                // than the visible part of the document in some mobile
+                // browsers when it's address bar is visible.
+                // https://developers.google.com/web/updates/2016/12/url-bar-resizing
+                height: `${document.body.clientHeight}px`,
+              }}
+              containerStyle={containerStyle}
+              onChangeIndex={this.handleSwipe}
+              onSwitching={this.handleSwitching}
+              index={index}
+            >
+              {content}
+            </ReactSwipeableViews>
+          </div>
+        </div>
+        <div className={navigationClassName}>
+          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
+          {leftNav}
+          {rightNav}
+          <ul className='media-modal__pagination'>
+            {pagination}
+          </ul>
         </div>
-        <ul className='media-modal__pagination'>
-          {pagination}
-        </ul>
-
-        {rightNav}
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js
index 5839ba40a..20bf21153 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.js
+++ b/app/javascript/mastodon/features/ui/components/modal_root.js
@@ -8,6 +8,7 @@ import MediaModal from './media_modal';
 import VideoModal from './video_modal';
 import BoostModal from './boost_modal';
 import ConfirmationModal from './confirmation_modal';
+import FocalPointModal from './focal_point_modal';
 import {
   OnboardingModal,
   MuteModal,
@@ -27,6 +28,7 @@ const MODAL_COMPONENTS = {
   'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
   'EMBED': EmbedModal,
   'LIST_EDITOR': ListEditor,
+  'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
 };
 
 export default class ModalRoot extends React.PureComponent {
diff --git a/app/javascript/mastodon/features/ui/components/report_modal.js b/app/javascript/mastodon/features/ui/components/report_modal.js
index b5dfa422e..3ae97646f 100644
--- a/app/javascript/mastodon/features/ui/components/report_modal.js
+++ b/app/javascript/mastodon/features/ui/components/report_modal.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import { connect } from 'react-redux';
-import { changeReportComment, submitReport } from '../../../actions/reports';
+import { changeReportComment, changeReportForward, submitReport } from '../../../actions/reports';
 import { refreshAccountTimeline } from '../../../actions/timelines';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
@@ -10,8 +10,11 @@ import StatusCheckBox from '../../report/containers/status_check_box_container';
 import { OrderedSet } from 'immutable';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Button from '../../../components/button';
+import Toggle from 'react-toggle';
+import IconButton from '../../../components/icon_button';
 
 const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
   placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
   submit: { id: 'report.submit', defaultMessage: 'Submit' },
 });
@@ -26,6 +29,7 @@ const makeMapStateToProps = () => {
       isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
       account: getAccount(state, accountId),
       comment: state.getIn(['reports', 'new', 'comment']),
+      forward: state.getIn(['reports', 'new', 'forward']),
       statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
     };
   };
@@ -42,14 +46,19 @@ export default class ReportModal extends ImmutablePureComponent {
     account: ImmutablePropTypes.map,
     statusIds: ImmutablePropTypes.orderedSet.isRequired,
     comment: PropTypes.string.isRequired,
+    forward: PropTypes.bool,
     dispatch: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
-  handleCommentChange = (e) => {
+  handleCommentChange = e => {
     this.props.dispatch(changeReportComment(e.target.value));
   }
 
+  handleForwardChange = e => {
+    this.props.dispatch(changeReportForward(e.target.checked));
+  }
+
   handleSubmit = () => {
     this.props.dispatch(submitReport());
   }
@@ -65,26 +74,25 @@ export default class ReportModal extends ImmutablePureComponent {
   }
 
   render () {
-    const { account, comment, intl, statusIds, isSubmitting } = this.props;
+    const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
 
     if (!account) {
       return null;
     }
 
+    const domain = account.get('acct').split('@')[1];
+
     return (
       <div className='modal-root__modal report-modal'>
         <div className='report-modal__target'>
+          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
           <FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
         </div>
 
         <div className='report-modal__container'>
-          <div className='report-modal__statuses'>
-            <div>
-              {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
-            </div>
-          </div>
-
           <div className='report-modal__comment'>
+            <p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:' /></p>
+
             <textarea
               className='setting-text light'
               placeholder={intl.formatMessage(messages.placeholder)}
@@ -92,11 +100,26 @@ export default class ReportModal extends ImmutablePureComponent {
               onChange={this.handleCommentChange}
               disabled={isSubmitting}
             />
+
+            {domain && (
+              <div>
+                <p><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
+
+                <div className='setting-toggle'>
+                  <Toggle id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
+                  <label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /></label>
+                </div>
+              </div>
+            )}
+
+            <Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
           </div>
-        </div>
 
-        <div className='report-modal__action-bar'>
-          <Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
+          <div className='report-modal__statuses'>
+            <div>
+              {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
+            </div>
+          </div>
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js
index 7694e5ab3..dba3be98b 100644
--- a/app/javascript/mastodon/features/ui/components/tabs_bar.js
+++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js
@@ -1,19 +1,18 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { NavLink } from 'react-router-dom';
+import { NavLink, withRouter } from 'react-router-dom';
 import { FormattedMessage, injectIntl } from 'react-intl';
 import { debounce } from 'lodash';
 import { isUserTouching } from '../../../is_mobile';
 
 export const links = [
-  <NavLink className='tabs-bar__link primary' to='/statuses/new' data-preview-title-id='tabs_bar.compose' data-preview-icon='pencil' ><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>,
   <NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
   <NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
 
   <NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
   <NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
 
-  <NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='asterisk' ><i className='fa fa-fw fa-asterisk' /></NavLink>,
+  <NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><i className='fa fa-fw fa-bars' /></NavLink>,
 ];
 
 export function getIndex (path) {
@@ -25,14 +24,12 @@ export function getLink (index) {
 }
 
 @injectIntl
-export default class TabsBar extends React.Component {
-
-  static contextTypes = {
-    router: PropTypes.object.isRequired,
-  }
+@withRouter
+export default class TabsBar extends React.PureComponent {
 
   static propTypes = {
     intl: PropTypes.object.isRequired,
+    history: PropTypes.object.isRequired,
   }
 
   setRef = ref => {
@@ -60,7 +57,7 @@ export default class TabsBar extends React.Component {
 
           const listener = debounce(() => {
             nextTab.removeEventListener('transitionend', listener);
-            this.context.router.history.push(to);
+            this.props.history.push(to);
           }, 50);
 
           nextTab.addEventListener('transitionend', listener);
diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js
index 6a883759f..9ed4a43ad 100644
--- a/app/javascript/mastodon/features/ui/components/video_modal.js
+++ b/app/javascript/mastodon/features/ui/components/video_modal.js
@@ -16,7 +16,7 @@ export default class VideoModal extends ImmutablePureComponent {
     const { media, time, onClose } = this.props;
 
     return (
-      <div className='modal-root__modal media-modal'>
+      <div className='modal-root__modal video-modal'>
         <div>
           <Video
             preview={media.get('preview_url')}
diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.js b/app/javascript/mastodon/features/ui/components/zoomable_image.js
new file mode 100644
index 000000000..0a0a4d41a
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/zoomable_image.js
@@ -0,0 +1,151 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const MIN_SCALE = 1;
+const MAX_SCALE = 4;
+
+const getMidpoint = (p1, p2) => ({
+  x: (p1.clientX + p2.clientX) / 2,
+  y: (p1.clientY + p2.clientY) / 2,
+});
+
+const getDistance = (p1, p2) =>
+  Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2));
+
+const clamp = (min, max, value) => Math.min(max, Math.max(min, value));
+
+export default class ZoomableImage extends React.PureComponent {
+
+  static propTypes = {
+    alt: PropTypes.string,
+    src: PropTypes.string.isRequired,
+    width: PropTypes.number,
+    height: PropTypes.number,
+    onClick: PropTypes.func,
+  }
+
+  static defaultProps = {
+    alt: '',
+    width: null,
+    height: null,
+  };
+
+  state = {
+    scale: MIN_SCALE,
+  }
+
+  removers = [];
+  container = null;
+  image = null;
+  lastTouchEndTime = 0;
+  lastDistance = 0;
+
+  componentDidMount () {
+    let handler = this.handleTouchStart;
+    this.container.addEventListener('touchstart', handler);
+    this.removers.push(() => this.container.removeEventListener('touchstart', handler));
+    handler = this.handleTouchMove;
+    // on Chrome 56+, touch event listeners will default to passive
+    // https://www.chromestatus.com/features/5093566007214080
+    this.container.addEventListener('touchmove', handler, { passive: false });
+    this.removers.push(() => this.container.removeEventListener('touchend', handler));
+  }
+
+  componentWillUnmount () {
+    this.removeEventListeners();
+  }
+
+  removeEventListeners () {
+    this.removers.forEach(listeners => listeners());
+    this.removers = [];
+  }
+
+  handleTouchStart = e => {
+    if (e.touches.length !== 2) return;
+
+    this.lastDistance = getDistance(...e.touches);
+  }
+
+  handleTouchMove = e => {
+    const { scrollTop, scrollHeight, clientHeight } = this.container;
+    if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
+      // prevent propagating event to MediaModal
+      e.stopPropagation();
+      return;
+    }
+    if (e.touches.length !== 2) return;
+
+    e.preventDefault();
+    e.stopPropagation();
+
+    const distance = getDistance(...e.touches);
+    const midpoint = getMidpoint(...e.touches);
+    const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance);
+
+    this.zoom(scale, midpoint);
+
+    this.lastMidpoint = midpoint;
+    this.lastDistance = distance;
+  }
+
+  zoom(nextScale, midpoint) {
+    const { scale } = this.state;
+    const { scrollLeft, scrollTop } = this.container;
+
+    // math memo:
+    // x = (scrollLeft + midpoint.x) / scrollWidth
+    // x' = (nextScrollLeft + midpoint.x) / nextScrollWidth
+    // scrollWidth = clientWidth * scale
+    // scrollWidth' = clientWidth * nextScale
+    // Solve x = x' for nextScrollLeft
+    const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x;
+    const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y;
+
+    this.setState({ scale: nextScale }, () => {
+      this.container.scrollLeft = nextScrollLeft;
+      this.container.scrollTop = nextScrollTop;
+    });
+  }
+
+  handleClick = e => {
+    // don't propagate event to MediaModal
+    e.stopPropagation();
+    const handler = this.props.onClick;
+    if (handler) handler();
+  }
+
+  setContainerRef = c => {
+    this.container = c;
+  }
+
+  setImageRef = c => {
+    this.image = c;
+  }
+
+  render () {
+    const { alt, src } = this.props;
+    const { scale } = this.state;
+    const overflow = scale === 1 ? 'hidden' : 'scroll';
+
+    return (
+      <div
+        className='zoomable-image'
+        ref={this.setContainerRef}
+        style={{ overflow }}
+      >
+        <img
+          role='presentation'
+          ref={this.setImageRef}
+          alt={alt}
+          src={src}
+          style={{
+            transform: `scale(${scale})`,
+            transformOrigin: '0 0',
+          }}
+          onClick={this.handleClick}
+        />
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js
index 59b53d823..fc2867cf0 100644
--- a/app/javascript/mastodon/features/ui/containers/status_list_container.js
+++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js
@@ -56,10 +56,7 @@ const makeMapStateToProps = () => {
 
 const mapDispatchToProps = (dispatch, { timelineId, loadMore }) => ({
 
-  onScrollToBottom: debounce(() => {
-    dispatch(scrollTopTimeline(timelineId, false));
-    loadMore();
-  }, 300, { leading: true }),
+  onLoadMore: debounce(loadMore, 300, { leading: true }),
 
   onScrollToTop: debounce(() => {
     dispatch(scrollTopTimeline(timelineId, true));
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 5b0d7246a..6cf00222a 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -1,3 +1,4 @@
+import classNames from 'classnames';
 import React from 'react';
 import NotificationsContainer from './containers/notifications_container';
 import PropTypes from 'prop-types';
@@ -55,6 +56,7 @@ const messages = defineMessages({
 const mapStateToProps = state => ({
   isComposing: state.getIn(['compose', 'is_composing']),
   hasComposingText: state.getIn(['compose', 'text']) !== '',
+  dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
 });
 
 const keyMap = {
@@ -84,10 +86,93 @@ const keyMap = {
   goToMuted: 'g m',
 };
 
+class SwitchingColumnsArea extends React.PureComponent {
+
+  static propTypes = {
+    children: PropTypes.node,
+    location: PropTypes.object,
+    onLayoutChange: PropTypes.func.isRequired,
+  };
+
+  state = {
+    mobile: isMobile(window.innerWidth),
+  };
+
+  componentWillMount () {
+    window.addEventListener('resize', this.handleResize, { passive: true });
+  }
+
+  componentDidUpdate (prevProps) {
+    if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
+      this.node.handleChildrenContentChange();
+    }
+  }
+
+  componentWillUnmount () {
+    window.removeEventListener('resize', this.handleResize);
+  }
+
+  handleResize = debounce(() => {
+    // The cached heights are no longer accurate, invalidate
+    this.props.onLayoutChange();
+
+    this.setState({ mobile: isMobile(window.innerWidth) });
+  }, 500, {
+    trailing: true,
+  });
+
+  setRef = c => {
+    this.node = c.getWrappedInstance().getWrappedInstance();
+  }
+
+  render () {
+    const { children } = this.props;
+    const { mobile } = this.state;
+
+    return (
+      <ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
+        <WrappedSwitch>
+          <Redirect from='/' to='/getting-started' exact />
+          <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
+          <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
+          <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
+          <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
+          <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
+          <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
+          <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
+
+          <WrappedRoute path='/notifications' component={Notifications} content={children} />
+          <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
+          <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
+
+          <WrappedRoute path='/statuses/new' component={Compose} content={children} />
+          <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
+          <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
+          <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
+
+          <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
+          <WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
+          <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
+          <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
+          <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
+
+          <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
+          <WrappedRoute path='/blocks' component={Blocks} content={children} />
+          <WrappedRoute path='/mutes' component={Mutes} content={children} />
+          <WrappedRoute path='/lists' component={Lists} content={children} />
+
+          <WrappedRoute component={GenericNotFound} content={children} />
+        </WrappedSwitch>
+      </ColumnsAreaContainer>
+    );
+  }
+
+}
+
 @connect(mapStateToProps)
 @injectIntl
 @withRouter
-export default class UI extends React.Component {
+export default class UI extends React.PureComponent {
 
   static contextTypes = {
     router: PropTypes.object.isRequired,
@@ -100,10 +185,10 @@ export default class UI extends React.Component {
     hasComposingText: PropTypes.bool,
     location: PropTypes.object,
     intl: PropTypes.object.isRequired,
+    dropdownMenuIsOpen: PropTypes.bool,
   };
 
   state = {
-    width: window.innerWidth,
     draggingOver: false,
   };
 
@@ -118,14 +203,10 @@ export default class UI extends React.Component {
     }
   }
 
-  handleResize = debounce(() => {
+  handleLayoutChange = () => {
     // The cached heights are no longer accurate, invalidate
     this.props.dispatch(clearHeight());
-
-    this.setState({ width: window.innerWidth });
-  }, 500, {
-    trailing: true,
-  });
+  }
 
   handleDragEnter = (e) => {
     e.preventDefault();
@@ -193,7 +274,6 @@ export default class UI extends React.Component {
 
   componentWillMount () {
     window.addEventListener('beforeunload', this.handleBeforeUnload, false);
-    window.addEventListener('resize', this.handleResize, { passive: true });
     document.addEventListener('dragenter', this.handleDragEnter, false);
     document.addEventListener('dragover', this.handleDragOver, false);
     document.addEventListener('drop', this.handleDrop, false);
@@ -214,28 +294,8 @@ export default class UI extends React.Component {
     };
   }
 
-  shouldComponentUpdate (nextProps) {
-    if (nextProps.isComposing !== this.props.isComposing) {
-      // Avoid expensive update just to toggle a class
-      this.node.classList.toggle('is-composing', nextProps.isComposing);
-
-      return false;
-    }
-
-    // Why isn't this working?!?
-    // return super.shouldComponentUpdate(nextProps, nextState);
-    return true;
-  }
-
-  componentDidUpdate (prevProps) {
-    if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
-      this.columnsAreaNode.handleChildrenContentChange();
-    }
-  }
-
   componentWillUnmount () {
     window.removeEventListener('beforeunload', this.handleBeforeUnload);
-    window.removeEventListener('resize', this.handleResize);
     document.removeEventListener('dragenter', this.handleDragEnter);
     document.removeEventListener('dragover', this.handleDragOver);
     document.removeEventListener('drop', this.handleDrop);
@@ -247,10 +307,6 @@ export default class UI extends React.Component {
     this.node = c;
   }
 
-  setColumnsAreaRef = c => {
-    this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
-  }
-
   handleHotkeyNew = e => {
     e.preventDefault();
 
@@ -350,8 +406,8 @@ export default class UI extends React.Component {
   }
 
   render () {
-    const { width, draggingOver } = this.state;
-    const { children } = this.props;
+    const { draggingOver } = this.state;
+    const { children, isComposing, location, dropdownMenuIsOpen } = this.props;
 
     const handlers = {
       help: this.handleHotkeyToggleHelp,
@@ -374,42 +430,12 @@ export default class UI extends React.Component {
 
     return (
       <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
-        <div className='ui' ref={this.setRef}>
+        <div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
           <TabsBar />
 
-          <ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width)}>
-            <WrappedSwitch>
-              <Redirect from='/' to='/getting-started' exact />
-              <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
-              <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
-              <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
-              <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
-              <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
-              <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
-              <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
-
-              <WrappedRoute path='/notifications' component={Notifications} content={children} />
-              <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
-              <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
-
-              <WrappedRoute path='/statuses/new' component={Compose} content={children} />
-              <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
-              <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
-              <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
-
-              <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
-              <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
-              <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
-              <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
-
-              <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
-              <WrappedRoute path='/blocks' component={Blocks} content={children} />
-              <WrappedRoute path='/mutes' component={Mutes} content={children} />
-              <WrappedRoute path='/lists' component={Lists} content={children} />
-
-              <WrappedRoute component={GenericNotFound} content={children} />
-            </WrappedSwitch>
-          </ColumnsAreaContainer>
+          <SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}>
+            {children}
+          </SwitchingColumnsArea>
 
           <NotificationsContainer />
           <LoadingBarContainer className='loading-bar' />
diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
index 43007ddc3..32dfe320b 100644
--- a/app/javascript/mastodon/features/ui/util/react_router_helpers.js
+++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
@@ -35,14 +35,19 @@ export class WrappedRoute extends React.Component {
     component: PropTypes.func.isRequired,
     content: PropTypes.node,
     multiColumn: PropTypes.bool,
-  }
+    componentParams: PropTypes.object,
+  };
+
+  static defaultProps = {
+    componentParams: {},
+  };
 
   renderComponent = ({ match }) => {
-    const { component, content, multiColumn } = this.props;
+    const { component, content, multiColumn, componentParams } = this.props;
 
     return (
       <BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}>
-        {Component => <Component params={match.params} multiColumn={multiColumn}>{content}</Component>}
+        {Component => <Component params={match.params} multiColumn={multiColumn} {...componentParams}>{content}</Component>}
       </BundleContainer>
     );
   }
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 0ee8bb6c8..98ebcb6f9 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/features/video/index.js
@@ -4,6 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { throttle } from 'lodash';
 import classNames from 'classnames';
 import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
+import { displaySensitiveMedia } from '../../initial_state';
 
 const messages = defineMessages({
   play: { id: 'video.play', defaultMessage: 'Play' },
@@ -29,7 +30,7 @@ const formatTime = secondsNum => {
   return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`;
 };
 
-const findElementPosition = el => {
+export const findElementPosition = el => {
   let box;
 
   if (el.getBoundingClientRect && el.parentNode) {
@@ -60,7 +61,7 @@ const findElementPosition = el => {
   };
 };
 
-const getPointerPosition = (el, event) => {
+export const getPointerPosition = (el, event) => {
   const position = {};
   const box = findElementPosition(el);
   const boxW = el.offsetWidth;
@@ -76,7 +77,7 @@ const getPointerPosition = (el, event) => {
     pageY = event.changedTouches[0].pageY;
   }
 
-  position.y = Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
+  position.y = Math.max(0, Math.min(1, (pageY - boxY) / boxH));
   position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
 
   return position;
@@ -96,6 +97,7 @@ export default class Video extends React.PureComponent {
     onOpenVideo: PropTypes.func,
     onCloseVideo: PropTypes.func,
     detailed: PropTypes.bool,
+    inline: PropTypes.bool,
     intl: PropTypes.object.isRequired,
   };
 
@@ -104,14 +106,21 @@ export default class Video extends React.PureComponent {
     duration: 0,
     paused: true,
     dragging: false,
+    containerWidth: false,
     fullscreen: false,
     hovered: false,
     muted: false,
-    revealed: !this.props.sensitive,
+    revealed: !this.props.sensitive || displaySensitiveMedia,
   };
 
   setPlayerRef = c => {
     this.player = c;
+
+    if (c) {
+      this.setState({
+        containerWidth: c.offsetWidth,
+      });
+    }
   }
 
   setVideoRef = c => {
@@ -245,12 +254,23 @@ export default class Video extends React.PureComponent {
   }
 
   render () {
-    const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
-    const { currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
+    const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
+    const { containerWidth, currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
     const progress = (currentTime / duration) * 100;
+    const playerStyle = {};
+
+    let { width, height } = this.props;
+
+    if (inline && containerWidth) {
+      width  = containerWidth;
+      height = containerWidth / (16/9);
+
+      playerStyle.width  = width;
+      playerStyle.height = height;
+    }
 
     return (
-      <div className={classNames('video-player', { inactive: !revealed, detailed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
+      <div className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen })} style={playerStyle} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
         <video
           ref={this.setVideoRef}
           src={src}
@@ -270,7 +290,7 @@ export default class Video extends React.PureComponent {
           onProgress={this.handleProgress}
         />
 
-        <button className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
+        <button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
           <span className='video-player__spoiler__title'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
           <span className='video-player__spoiler__subtitle'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
         </button>
@@ -289,10 +309,10 @@ export default class Video extends React.PureComponent {
 
           <div className='video-player__buttons-bar'>
             <div className='video-player__buttons left'>
-              <button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
-              <button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
+              <button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
+              <button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
 
-              {!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>}
+              {!onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>}
 
               {(detailed || fullscreen) &&
                 <span>
@@ -304,9 +324,9 @@ export default class Video extends React.PureComponent {
             </div>
 
             <div className='video-player__buttons right'>
-              {(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>}
-              {onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>}
-              <button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button>
+              {(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>}
+              {onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>}
+              <button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button>
             </div>
           </div>
         </div>
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 3fc45077d..df310e7e1 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -5,9 +5,11 @@ const getMeta = (prop) => initialState && initialState.meta && initialState.meta
 
 export const reduceMotion = getMeta('reduce_motion');
 export const autoPlayGif = getMeta('auto_play_gif');
+export const displaySensitiveMedia = getMeta('display_sensitive_media');
 export const unfollowModal = getMeta('unfollow_modal');
 export const boostModal = getMeta('boost_modal');
 export const deleteModal = getMeta('delete_modal');
 export const me = getMeta('me');
+export const searchEnabled = getMeta('search_enabled');
 
 export default initialState;
diff --git a/app/javascript/mastodon/load_polyfills.js b/app/javascript/mastodon/load_polyfills.js
index 8927b7358..815e1905b 100644
--- a/app/javascript/mastodon/load_polyfills.js
+++ b/app/javascript/mastodon/load_polyfills.js
@@ -14,6 +14,7 @@ function loadPolyfills() {
   const needsBasePolyfills = !(
     window.Intl &&
     Object.assign &&
+    Object.values &&
     Number.isNaN &&
     window.Symbol &&
     Array.prototype.includes
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index 795b27707..33e223b2a 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -1,7 +1,9 @@
 {
   "account.block": "حظر @{name}",
   "account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}",
+  "account.blocked": "محظور",
   "account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "تعديل الملف الشخصي",
   "account.follow": "تابِع",
   "account.followers": "المتابعون",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} إنتقل إلى :",
   "account.mute": "أكتم @{name}",
   "account.mute_notifications": "كتم إخطارات @{name}",
-  "account.posts": "المشاركات",
+  "account.muted": "مكتوم",
+  "account.posts": "التبويقات",
+  "account.posts_with_replies": "تبويقات تحتوي على رُدود",
   "account.report": "أبلغ عن @{name}",
   "account.requested": "في انتظار الموافقة",
   "account.share": "مشاركة @{name}'s profile",
@@ -50,7 +54,7 @@
   "column_header.unpin": "فك التدبيس",
   "column_subheading.navigation": "التصفح",
   "column_subheading.settings": "الإعدادات",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "هذا التبويق لن يُدرَج تحت أي وسم كان بما أنه غير مُدرَج. لا يُسمح بالبحث إلّا عن التبويقات العمومية عن طريق الوسوم.",
   "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.",
   "compose_form.lock_disclaimer.lock": "مقفل",
   "compose_form.placeholder": "فيمَ تفكّر؟",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "تحميل ...",
   "media_gallery.toggle_visible": "عرض / إخفاء",
   "missing_indicator.label": "تعذر العثور عليه",
+  "missing_indicator.sublabel": "تعذر العثور على هذا المورد",
   "mute_modal.hide_notifications": "هل تود إخفاء الإخطارات القادمة من هذا المستخدم ؟",
   "navigation_bar.blocks": "الحسابات المحجوبة",
   "navigation_bar.community_timeline": "الخيط العام المحلي",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "تعرض الصفحة الرئيسية منشورات جميع الأشخاص الذين تتابعهم.",
   "onboarding.page_four.notifications": "فعندما يتفاعل شخص ما معك، عمود الإخطارات يخبرك.",
   "onboarding.page_one.federation": "ماستدون شبكة من خوادم مستقلة متلاحمة تهدف إلى إنشاء أكبر شبكة اجتماعية موحدة. تسمى هذه السرفيرات بمثيلات خوادم.",
-  "onboarding.page_one.handle": "أنت الآن على {domain}، واحد من مجموع مثيلات الخوادم المستقلة. اسم المستخدم الكامل الخاص بك هو {handle}",
+  "onboarding.page_one.full_handle": "عنوانك الكامل",
+  "onboarding.page_one.handle_hint": "هذا هو ما يجب عليك توصيله لأصدقائك للبحث عنه.",
   "onboarding.page_one.welcome": "مرحبا بك في ماستدون !",
   "onboarding.page_six.admin": "مدير(ة) مثيل الخادم هذا {admin}.",
   "onboarding.page_six.almost_done": "أنهيت تقريبا ...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "للعامة",
   "privacy.unlisted.long": "لا تقم بإدراجه على الخيوط العامة",
   "privacy.unlisted.short": "غير مدرج",
+  "regeneration_indicator.label": "جارٍ التحميل …",
+  "regeneration_indicator.sublabel": "جارٍ تجهيز تغذية صفحتك الرئيسية !",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "الآن",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "إلغاء",
+  "report.forward": "التحويل إلى {target}",
+  "report.forward_hint": "هذا الحساب ينتمي إلى خادوم آخَر. هل تودّ إرسال نسخة مجهولة مِن التقرير إلى هنالك أيضًا ؟",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "تعليقات إضافية",
   "report.submit": "إرسال",
   "report.target": "إبلاغ",
   "search.placeholder": "ابحث",
   "search_popout.search_format": "نمط البحث المتقدم",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "وسم",
   "search_popout.tips.status": "حالة",
   "search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية",
   "search_popout.tips.user": "مستخدِم",
+  "search_results.accounts": "أشخاص",
+  "search_results.hashtags": "الوُسوم",
+  "search_results.statuses": "التبويقات",
   "search_results.total": "{count, number} {count, plural, one {result} و {results}}",
   "standalone.public_title": "نظرة على ...",
   "status.block": "Block @{name}",
@@ -223,10 +238,11 @@
   "status.media_hidden": "الصورة مستترة",
   "status.mention": "أذكُر @{name}",
   "status.more": "المزيد",
-  "status.mute": "Mute @{name}",
+  "status.mute": "أكتم @{name}",
   "status.mute_conversation": "كتم المحادثة",
   "status.open": "وسع هذه المشاركة",
   "status.pin": "تدبيس على الملف الشخصي",
+  "status.pinned": "تبويق مثبَّت",
   "status.reblog": "رَقِّي",
   "status.reblogged_by": "{name} رقى",
   "status.reply": "ردّ",
@@ -239,7 +255,6 @@
   "status.show_more": "أظهر المزيد",
   "status.unmute_conversation": "فك الكتم عن المحادثة",
   "status.unpin": "فك التدبيس من الملف الشخصي",
-  "tabs_bar.compose": "تحرير",
   "tabs_bar.federated_timeline": "الموحَّد",
   "tabs_bar.home": "الرئيسية",
   "tabs_bar.local_timeline": "المحلي",
@@ -248,6 +263,7 @@
   "upload_area.title": "إسحب ثم أفلت للرفع",
   "upload_button.label": "إضافة وسائط",
   "upload_form.description": "وصف للمعاقين بصريا",
+  "upload_form.focus": "قص",
   "upload_form.undo": "إلغاء",
   "upload_progress.label": "يرفع...",
   "video.close": "إغلاق الفيديو",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index c0a24dacb..a84e6e9d1 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Блокирай",
   "account.block_domain": "Hide everything from {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Редактирай профила си",
   "account.follow": "Последвай",
   "account.followers": "Последователи",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
   "account.posts": "Публикации",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Report @{name}",
   "account.requested": "В очакване на одобрение",
   "account.share": "Share @{name}'s profile",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Зареждане...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
   "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
   "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
-  "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Welcome to Mastodon!",
   "onboarding.page_six.admin": "Your instance's admin is {admin}.",
   "onboarding.page_six.almost_done": "Almost done...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not show in public timelines",
   "privacy.unlisted.short": "Unlisted",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Отказ",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Additional comments",
   "report.submit": "Submit",
   "report.target": "Reporting",
   "search.placeholder": "Търсене",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Mute conversation",
   "status.open": "Expand this status",
   "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Споделяне",
   "status.reblogged_by": "{name} сподели",
   "status.reply": "Отговор",
@@ -239,7 +255,6 @@
   "status.show_more": "Show more",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Съставяне",
   "tabs_bar.federated_timeline": "Federated",
   "tabs_bar.home": "Начало",
   "tabs_bar.local_timeline": "Local",
@@ -248,6 +263,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Добави медия",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Отмяна",
   "upload_progress.label": "Uploading...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 3eb0e3d26..bac807dbb 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -1,29 +1,33 @@
 {
-  "account.block": "Bloquejar @{name}",
-  "account.block_domain": "Amagar tot de {domain}",
+  "account.block": "Bloca @{name}",
+  "account.block_domain": "Amaga-ho tot de {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.",
-  "account.edit_profile": "Editar perfil",
-  "account.follow": "Seguir",
+  "account.domain_blocked": "Domain hidden",
+  "account.edit_profile": "Edita el perfil",
+  "account.follow": "Segueix",
   "account.followers": "Seguidors",
   "account.follows": "Seguint",
-  "account.follows_you": "et segueix",
+  "account.follows_you": "Et segueix",
   "account.hide_reblogs": "Amaga els impulsos de @{name}",
   "account.media": "Media",
   "account.mention": "Esmentar @{name}",
   "account.moved_to": "{name} s'ha mogut a:",
-  "account.mute": "Silenciar @{name}",
+  "account.mute": "Silencia @{name}",
   "account.mute_notifications": "Notificacions desactivades de @{name}",
-  "account.posts": "Publicacions",
+  "account.muted": "Muted",
+  "account.posts": "Toots",
+  "account.posts_with_replies": "Toots amb respostes",
   "account.report": "Informe @{name}",
   "account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment",
-  "account.share": "Compartir el perfil de @{name}",
+  "account.share": "Comparteix el perfil de @{name}",
   "account.show_reblogs": "Mostra els impulsos de @{name}",
-  "account.unblock": "Desbloquejar @{name}",
+  "account.unblock": "Desbloca @{name}",
   "account.unblock_domain": "Mostra {domain}",
-  "account.unfollow": "Deixar de seguir",
+  "account.unfollow": "Deixa de seguir",
   "account.unmute": "Treure silenci de @{name}",
   "account.unmute_notifications": "Activar notificacions de @{name}",
-  "account.view_full_profile": "Veure el perfil complet",
+  "account.view_full_profile": "Mostra el perfil complet",
   "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
   "bundle_column_error.body": "S'ha produït un error en carregar aquest component.",
   "bundle_column_error.retry": "Torna-ho a provar",
@@ -31,7 +35,7 @@
   "bundle_modal_error.close": "Tanca",
   "bundle_modal_error.message": "S'ha produït un error en carregar aquest component.",
   "bundle_modal_error.retry": "Torna-ho a provar",
-  "column.blocks": "Usuaris bloquejats",
+  "column.blocks": "Usuaris blocats",
   "column.community": "Línia de temps local",
   "column.favourites": "Favorits",
   "column.follow_requests": "Peticions per seguir-te",
@@ -45,46 +49,46 @@
   "column_header.hide_settings": "Amaga la configuració",
   "column_header.moveLeft_settings": "Mou la columna cap a l'esquerra",
   "column_header.moveRight_settings": "Mou la columna cap a la dreta",
-  "column_header.pin": "Fixar",
+  "column_header.pin": "Fixa",
   "column_header.show_settings": "Mostra la configuració",
-  "column_header.unpin": "Deslligar",
+  "column_header.unpin": "No fixis",
   "column_subheading.navigation": "Navegació",
   "column_subheading.settings": "Configuració",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "Aquest toot no es mostrarà en cap etiqueta ja que no està llistat. Només els toots públics poden ser cercats per etiqueta.",
   "compose_form.lock_disclaimer": "El teu compte no està bloquejat {locked}. Tothom pot seguir-te i veure els teus missatges a seguidors.",
-  "compose_form.lock_disclaimer.lock": "bloquejat",
+  "compose_form.lock_disclaimer.lock": "blocat",
   "compose_form.placeholder": "En què estàs pensant?",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive": "Marcar multimèdia com a sensible",
-  "compose_form.spoiler": "Amagar text darrera l'advertència",
-  "compose_form.spoiler_placeholder": "Escriu l'advertència aquí",
-  "confirmation_modal.cancel": "Cancel·lar",
-  "confirmations.block.confirm": "Bloquejar",
-  "confirmations.block.message": "Estàs segur que vols bloquejar {name}?",
-  "confirmations.delete.confirm": "Esborrar",
-  "confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?",
-  "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Estàs segur que vols esborrar permanenment aquesta llista?",
-  "confirmations.domain_block.confirm": "Amagar tot el domini",
-  "confirmations.domain_block.message": "Estàs realment, realment segur que vols bloquejar totalment {domain}? En la majoria dels casos bloquejar o silenciar és suficient i preferible.",
-  "confirmations.mute.confirm": "Silenciar",
+  "compose_form.sensitive": "Marca el contingut multimèdia com a sensible",
+  "compose_form.spoiler": "Amaga el text darrera darrere un avís",
+  "compose_form.spoiler_placeholder": "Escriu l'avís aquí",
+  "confirmation_modal.cancel": "Cancel·la",
+  "confirmations.block.confirm": "Bloca",
+  "confirmations.block.message": "Estàs segur que vols blocar {name}?",
+  "confirmations.delete.confirm": "Suprimeix",
+  "confirmations.delete.message": "Estàs segur que vols suprimir aquest estat?",
+  "confirmations.delete_list.confirm": "Suprimeix",
+  "confirmations.delete_list.message": "Estàs segur que vols suprimir permanentment aquesta llista?",
+  "confirmations.domain_block.confirm": "Amaga tot el domini",
+  "confirmations.domain_block.message": "Estàs realment, realment segur que vols blocar totalment {domain}? En la majoria dels casos blocar o silenciar uns pocs objectius és suficient i preferible.",
+  "confirmations.mute.confirm": "Silencia",
   "confirmations.mute.message": "Estàs segur que vols silenciar {name}?",
-  "confirmations.unfollow.confirm": "Deixar de seguir",
+  "confirmations.unfollow.confirm": "Deixa de seguir",
   "confirmations.unfollow.message": "Estàs segur que vols deixar de seguir {name}?",
   "embed.instructions": "Incrusta aquest estat al lloc web copiant el codi a continuació.",
   "embed.preview": "Aquí tenim quin aspecte tindrá:",
   "emoji_button.activity": "Activitat",
   "emoji_button.custom": "Personalitzat",
-  "emoji_button.flags": "Marques",
-  "emoji_button.food": "Menjar i Beure",
-  "emoji_button.label": "Inserir emoji",
+  "emoji_button.flags": "Banderes",
+  "emoji_button.food": "Menjar i beure",
+  "emoji_button.label": "Insereix un emoji",
   "emoji_button.nature": "Natura",
   "emoji_button.not_found": "Emojos no!! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "Objectes",
   "emoji_button.people": "Gent",
-  "emoji_button.recent": "Freqüentment utilitzat",
-  "emoji_button.search": "Cercar...",
+  "emoji_button.recent": "Usats freqüentment",
+  "emoji_button.search": "Cerca...",
   "emoji_button.search_results": "Resultats de la cerca",
   "emoji_button.symbols": "Símbols",
   "emoji_button.travel": "Viatges i Llocs",
@@ -100,7 +104,7 @@
   "getting_started.appsshort": "Aplicacions",
   "getting_started.faq": "PMF",
   "getting_started.heading": "Començant",
-  "getting_started.open_source_notice": "Mastodon és un programari de codi obert. Pots contribuir o informar de problemes a GitHub de {github}.",
+  "getting_started.open_source_notice": "Mastodon és un programari de codi obert. Pots contribuir o informar de problemes a GitHub a {github}.",
   "getting_started.userguide": "Guia de l'usuari",
   "home.column_settings.advanced": "Avançat",
   "home.column_settings.basic": "Bàsic",
@@ -114,7 +118,7 @@
   "keyboard_shortcuts.compose": "per centrar l'area de composició de text",
   "keyboard_shortcuts.description": "Description",
   "keyboard_shortcuts.down": "per baixar en la llista",
-  "keyboard_shortcuts.enter": "to open status",
+  "keyboard_shortcuts.enter": "ampliar estat",
   "keyboard_shortcuts.favourite": "afavorir",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
   "keyboard_shortcuts.hotkey": "Tecla d'accés directe",
@@ -133,12 +137,13 @@
   "lists.delete": "Delete list",
   "lists.edit": "Editar llista",
   "lists.new.create": "Afegir llista",
-  "lists.new.title_placeholder": "Nou títol de llista",
+  "lists.new.title_placeholder": "Nova llista",
   "lists.search": "Cercar entre les persones que segueixes",
   "lists.subheading": "Les teves llistes",
   "loading_indicator.label": "Carregant...",
   "media_gallery.toggle_visible": "Alternar visibilitat",
   "missing_indicator.label": "No trobat",
+  "missing_indicator.sublabel": "Aquest recurs no pot ser trobat",
   "mute_modal.hide_notifications": "Amagar notificacions d'aquest usuari?",
   "navigation_bar.blocks": "Usuaris bloquejats",
   "navigation_bar.community_timeline": "Línia de temps Local",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "La línia de temps d'Inici mostra missatges de les persones que segueixes.",
   "onboarding.page_four.notifications": "La columna Notificacions mostra quan algú interactua amb tu.",
   "onboarding.page_one.federation": "Mastodon és una xarxa de servidors independents que s'uneixen per fer una més gran xarxa social. A aquests servidors els hi diem instàncies.",
-  "onboarding.page_one.handle": "Ets a {domain}, per tant el teu usuari complert és {handle}",
+  "onboarding.page_one.full_handle": "El teu usuari complet",
+  "onboarding.page_one.handle_hint": "Això és el que els hi diries als teus amics que cerquin.",
   "onboarding.page_one.welcome": "Benvingut a Mastodon!",
   "onboarding.page_six.admin": "L'administrador de la teva instància és {admin}.",
   "onboarding.page_six.almost_done": "Quasi fet...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Públic",
   "privacy.unlisted.long": "No publicar en línies de temps públiques",
   "privacy.unlisted.short": "No llistat",
+  "regeneration_indicator.label": "Carregant…",
+  "regeneration_indicator.sublabel": "S'està preparant la línia de temps Inici!",
   "relative_time.days": "fa {number} dies",
   "relative_time.hours": "fa {number} hores",
   "relative_time.just_now": "ara",
   "relative_time.minutes": "fa {number} minuts",
   "relative_time.seconds": "fa {number} segons",
   "reply_indicator.cancel": "Cancel·lar",
+  "report.forward": "Reenvia a {target}",
+  "report.forward_hint": "Aquest compte és d'un altre servidor. Enviar-hi també una copia anònima del informe?",
+  "report.hint": "El informe s'enviarà als moderadors de la teva instància. Pots explicar perquè vols informar d'aquest compte aquí:",
   "report.placeholder": "Comentaris addicionals",
   "report.submit": "Enviar",
   "report.target": "Informes",
   "search.placeholder": "Cercar",
   "search_popout.search_format": "Format de cerca avançada",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "etiqueta",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "El text simple retorna coincidències amb els noms de visualització, els noms d'usuari i els hashtags",
   "search_popout.tips.user": "usuari",
+  "search_results.accounts": "Gent",
+  "search_results.hashtags": "Etiquetes",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, un {result} altres {results}}",
   "standalone.public_title": "Una mirada a l'interior ...",
   "status.block": "Block @{name}",
@@ -223,10 +238,11 @@
   "status.media_hidden": "Multimèdia amagat",
   "status.mention": "Esmentar @{name}",
   "status.more": "Més",
-  "status.mute": "Mute @{name}",
+  "status.mute": "Silenciar @{name}",
   "status.mute_conversation": "Silenciar conversació",
   "status.open": "Ampliar aquest estat",
   "status.pin": "Fixat en el perfil",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Impuls",
   "status.reblogged_by": "{name} ha retootejat",
   "status.reply": "Respondre",
@@ -239,7 +255,6 @@
   "status.show_more": "Mostra més",
   "status.unmute_conversation": "Activar conversació",
   "status.unpin": "Deslliga del perfil",
-  "tabs_bar.compose": "Compondre",
   "tabs_bar.federated_timeline": "Federada",
   "tabs_bar.home": "Inici",
   "tabs_bar.local_timeline": "Local",
@@ -248,6 +263,7 @@
   "upload_area.title": "Arrossega i deixa anar per carregar",
   "upload_button.label": "Afegir multimèdia",
   "upload_form.description": "Descriure els problemes visuals",
+  "upload_form.focus": "Retallar",
   "upload_form.undo": "Desfer",
   "upload_progress.label": "Pujant...",
   "video.close": "Tancar el vídeo",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index 9b6c857e4..6267b1d71 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -1,7 +1,9 @@
 {
   "account.block": "@{name} blocken",
   "account.block_domain": "Alles von {domain} verstecken",
+  "account.blocked": "Blockiert",
   "account.disclaimer_full": "Das Profil wird möglicherweise unvollständig wiedergegeben.",
+  "account.domain_blocked": "Domain versteckt",
   "account.edit_profile": "Profil bearbeiten",
   "account.follow": "Folgen",
   "account.followers": "Folgende",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} ist umgezogen auf:",
   "account.mute": "@{name} stummschalten",
   "account.mute_notifications": "Benachrichtigungen von @{name} verbergen",
+  "account.muted": "Stummgeschaltet",
   "account.posts": "Beiträge",
+  "account.posts_with_replies": "Beiträge mit Antworten",
   "account.report": "@{name} melden",
   "account.requested": "Warte auf Erlaubnis. Klicke zum Abbrechen",
   "account.share": "Profil von @{name} teilen",
@@ -36,7 +40,7 @@
   "column.favourites": "Favoriten",
   "column.follow_requests": "Folgeanfragen",
   "column.home": "Startseite",
-  "column.lists": "Lists",
+  "column.lists": "Listen",
   "column.mutes": "Stummgeschaltete Profile",
   "column.notifications": "Mitteilungen",
   "column.pins": "Angeheftete Beiträge",
@@ -50,7 +54,7 @@
   "column_header.unpin": "Lösen",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Einstellungen",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "Dieser Beitrag wird nicht unter einen dieser Hashtags sichtbar sein, solange er ungelistet ist. Bei einer Suche kann er nicht gefunden werden.",
   "compose_form.lock_disclaimer": "Dein Profil ist nicht {locked}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.",
   "compose_form.lock_disclaimer.lock": "gesperrt",
   "compose_form.placeholder": "Worüber möchtest du schreiben?",
@@ -65,7 +69,7 @@
   "confirmations.delete.confirm": "Löschen",
   "confirmations.delete.message": "Bist du dir sicher, dass du diesen Beitrag löschen möchtest?",
   "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+  "confirmations.delete_list.message": "Bist du dir sicher, dass du diese Liste permanent löschen möchtest?",
   "confirmations.domain_block.confirm": "Die ganze Domain verbergen",
   "confirmations.domain_block.message": "Bist du dir wirklich sicher, dass du die ganze Domain {domain} verbergen willst? In den meisten Fällen reichen ein paar gezielte Blocks aus.",
   "confirmations.mute.confirm": "Stummschalten",
@@ -92,7 +96,7 @@
   "empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.",
   "empty_column.home": "Deine Startseite ist leer! Besuche {public} oder nutze die Suche, um loszulegen und andere Leute zu finden.",
   "empty_column.home.public_timeline": "die öffentliche Zeitleiste",
-  "empty_column.list": "Diese Liste ist derzeit leer.",
+  "empty_column.list": "Diese Liste ist derzeit leer. Wenn Wesen auf dieser Liste neue Beiträge veröffentlichen werden sie hier erscheinen.",
   "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um ins Gespräch zu kommen.",
   "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Instanzen, um die Zeitleiste aufzufüllen",
   "follow_request.authorize": "Erlauben",
@@ -111,34 +115,35 @@
   "keyboard_shortcuts.back": "zurück navigieren",
   "keyboard_shortcuts.boost": "boosten",
   "keyboard_shortcuts.column": "einen Status in einer der Spalten fokussieren",
-  "keyboard_shortcuts.compose": "to focus the compose textarea",
-  "keyboard_shortcuts.description": "Description",
+  "keyboard_shortcuts.compose": "um das Textfeld zu fokussieren",
+  "keyboard_shortcuts.description": "Beschreibung",
   "keyboard_shortcuts.down": "sich in der Liste hinunter bewegen",
-  "keyboard_shortcuts.enter": "to open status",
-  "keyboard_shortcuts.favourite": "favorisieren",
-  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
+  "keyboard_shortcuts.enter": "um den Status zu öffnen",
+  "keyboard_shortcuts.favourite": "um zu favorisieren",
+  "keyboard_shortcuts.heading": "Tastenkombinationen",
   "keyboard_shortcuts.hotkey": "Hotkey",
-  "keyboard_shortcuts.legend": "diese Übersicht anzeigen",
-  "keyboard_shortcuts.mention": "Autor_in erwähnen",
-  "keyboard_shortcuts.reply": "antworten",
-  "keyboard_shortcuts.search": "die Suche fokussieren",
-  "keyboard_shortcuts.toot": "einen neuen Toot beginnen",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
+  "keyboard_shortcuts.legend": "um diese Übersicht anzuzeigen",
+  "keyboard_shortcuts.mention": "um Autor_in zu erwähnen",
+  "keyboard_shortcuts.reply": "um zu antworten",
+  "keyboard_shortcuts.search": "um die Suche zu fokussieren",
+  "keyboard_shortcuts.toot": "um einen neuen Toot zu beginnen",
+  "keyboard_shortcuts.unfocus": "um das Textfeld/die Suche nicht mehr zu fokussieren",
   "keyboard_shortcuts.up": "sich in der Liste hinauf bewegen",
   "lightbox.close": "Schließen",
   "lightbox.next": "Weiter",
   "lightbox.previous": "Zurück",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
+  "lists.account.add": "Zur Liste hinzufügen",
+  "lists.account.remove": "Von der Liste entfernen",
   "lists.delete": "Delete list",
-  "lists.edit": "Edit list",
-  "lists.new.create": "Add list",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Your lists",
+  "lists.edit": "Liste bearbeiten",
+  "lists.new.create": "Liste hinzufügen",
+  "lists.new.title_placeholder": "Neuer Titel der Liste",
+  "lists.search": "Suche nach Leuten denen du folgst",
+  "lists.subheading": "Deine Listen",
   "loading_indicator.label": "Wird geladen …",
   "media_gallery.toggle_visible": "Sichtbarkeit umschalten",
   "missing_indicator.label": "Nicht gefunden",
+  "missing_indicator.sublabel": "Die Ressource konnte nicht gefunden werden",
   "mute_modal.hide_notifications": "Benachrichtigungen von diesem Account verbergen?",
   "navigation_bar.blocks": "Blockierte Profile",
   "navigation_bar.community_timeline": "Lokale Zeitleiste",
@@ -147,7 +152,7 @@
   "navigation_bar.follow_requests": "Folgeanfragen",
   "navigation_bar.info": "Über diese Instanz",
   "navigation_bar.keyboard_shortcuts": "Tastenkombinationen",
-  "navigation_bar.lists": "Lists",
+  "navigation_bar.lists": "Listen",
   "navigation_bar.logout": "Abmelden",
   "navigation_bar.mutes": "Stummgeschaltete Profile",
   "navigation_bar.pins": "Angeheftete Beiträge",
@@ -170,11 +175,12 @@
   "notifications.column_settings.sound": "Ton abspielen",
   "onboarding.done": "Fertig",
   "onboarding.next": "Weiter",
-  "onboarding.page_five.public_timelines": "Die lokale Zeitleiste zeigt alle Beiträge von Leuten, die auch auf {domain} sind. Das gesamte bekannte Netz zeigt Beiträge von allen, denen von Leuten auf {domain} gefolgt wird. Zusammen sind sie die öffentlichen Zeitleisten. In ihnen kannst du viel Neues entdecken!",
+  "onboarding.page_five.public_timelines": "Die lokale Zeitleiste zeigt alle Beiträge von Leuten, die auch auf {domain} sind. Das gesamte bekannte Netz zeigt Beiträge von allen, denen von Leuten auf {domain} gefolgt wird. Zusammen sind sie die öffentlichen Zeitleisten, ein guter Weg, um neue Leute zu finden.",
   "onboarding.page_four.home": "Die Startseite zeigt dir Beiträge von Leuten, denen du folgst.",
   "onboarding.page_four.notifications": "Wenn jemand mit dir interagiert, bekommst du eine Mitteilung.",
   "onboarding.page_one.federation": "Mastodon ist ein soziales Netzwerk, das aus unabhängigen Servern besteht. Diese Server nennen wir auch Instanzen.",
-  "onboarding.page_one.handle": "Du bist auf der Instanz {domain}, also ist dein vollständiger Profilname im Netzwerk {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Willkommen bei Mastodon!",
   "onboarding.page_six.admin": "Für deine Instanz ist {admin} zuständig.",
   "onboarding.page_six.almost_done": "Fast fertig …",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Öffentlich",
   "privacy.unlisted.long": "Nicht in öffentlichen Zeitleisten anzeigen",
   "privacy.unlisted.short": "Nicht gelistet",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Abbrechen",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Zusätzliche Kommentare",
   "report.submit": "Absenden",
   "report.target": "{target} melden",
   "search.placeholder": "Suche",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
   "standalone.public_title": "Ein kleiner Einblick …",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Thread stummschalten",
   "status.open": "Diesen Beitrag öffnen",
   "status.pin": "Im Profil anheften",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Teilen",
   "status.reblogged_by": "{name} teilte",
   "status.reply": "Antworten",
@@ -239,7 +255,6 @@
   "status.show_more": "Mehr anzeigen",
   "status.unmute_conversation": "Stummschaltung von Thread aufheben",
   "status.unpin": "Vom Profil lösen",
-  "tabs_bar.compose": "Schreiben",
   "tabs_bar.federated_timeline": "Föderation",
   "tabs_bar.home": "Startseite",
   "tabs_bar.local_timeline": "Lokal",
@@ -248,6 +263,7 @@
   "upload_area.title": "Zum Hochladen hereinziehen",
   "upload_button.label": "Mediendatei hinzufügen",
   "upload_form.description": "Für Menschen mit Sehbehinderung beschreiben",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Entfernen",
   "upload_progress.label": "Wird hochgeladen …",
   "video.close": "Video schließen",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index acf051de5..c8ebf4b87 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -136,6 +136,10 @@
       {
         "defaultMessage": "Not found",
         "id": "missing_indicator.label"
+      },
+      {
+        "defaultMessage": "This resource could not be found",
+        "id": "missing_indicator.sublabel"
       }
     ],
     "path": "app/javascript/mastodon/components/missing_indicator.json"
@@ -258,6 +262,23 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Loading…",
+        "id": "regeneration_indicator.label"
+      },
+      {
+        "defaultMessage": "Your home feed is being prepared!",
+        "id": "regeneration_indicator.sublabel"
+      }
+    ],
+    "path": "app/javascript/mastodon/components/status_list.json"
+  },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "Pinned toot",
+        "id": "status.pinned"
+      },
+      {
         "defaultMessage": "{name} boosted",
         "id": "status.reblogged_by"
       }
@@ -301,11 +322,19 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Toots",
+        "id": "account.posts"
+      },
+      {
+        "defaultMessage": "Toots with replies",
+        "id": "account.posts_with_replies"
+      },
+      {
         "defaultMessage": "Media",
         "id": "account.media"
       }
     ],
-    "path": "app/javascript/mastodon/features/account_gallery/index.json"
+    "path": "app/javascript/mastodon/features/account_timeline/components/header.json"
   },
   {
     "descriptors": [
@@ -416,7 +445,7 @@
         "id": "account.view_full_profile"
       },
       {
-        "defaultMessage": "Posts",
+        "defaultMessage": "Toots",
         "id": "account.posts"
       },
       {
@@ -445,8 +474,24 @@
         "id": "account.requested"
       },
       {
+        "defaultMessage": "Unblock @{name}",
+        "id": "account.unblock"
+      },
+      {
         "defaultMessage": "Follows you",
         "id": "account.follows_you"
+      },
+      {
+        "defaultMessage": "Blocked",
+        "id": "account.blocked"
+      },
+      {
+        "defaultMessage": "Muted",
+        "id": "account.muted"
+      },
+      {
+        "defaultMessage": "Domain hidden",
+        "id": "account.domain_blocked"
       }
     ],
     "path": "app/javascript/mastodon/features/account/components/header.json"
@@ -634,6 +679,18 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "People",
+        "id": "search_results.accounts"
+      },
+      {
+        "defaultMessage": "Toots",
+        "id": "search_results.statuses"
+      },
+      {
+        "defaultMessage": "Hashtags",
+        "id": "search_results.hashtags"
+      },
+      {
         "defaultMessage": "{count, number} {count, plural, one {result} other {results}}",
         "id": "search_results.total"
       }
@@ -647,6 +704,14 @@
         "id": "search.placeholder"
       },
       {
+        "defaultMessage": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+        "id": "search_popout.tips.full_text"
+      },
+      {
+        "defaultMessage": "Simple text returns matching display names, usernames and hashtags",
+        "id": "search_popout.tips.text"
+      },
+      {
         "defaultMessage": "Advanced search format",
         "id": "search_popout.search_format"
       },
@@ -661,10 +726,6 @@
       {
         "defaultMessage": "status",
         "id": "search_popout.tips.status"
-      },
-      {
-        "defaultMessage": "Simple text returns matching display names, usernames and hashtags",
-        "id": "search_popout.tips.text"
       }
     ],
     "path": "app/javascript/mastodon/features/compose/components/search.json"
@@ -690,12 +751,16 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Describe for the visually impaired",
+        "id": "upload_form.description"
+      },
+      {
         "defaultMessage": "Undo",
         "id": "upload_form.undo"
       },
       {
-        "defaultMessage": "Describe for the visually impaired",
-        "id": "upload_form.description"
+        "defaultMessage": "Crop",
+        "id": "upload_form.focus"
       }
     ],
     "path": "app/javascript/mastodon/features/compose/components/upload.json"
@@ -1220,6 +1285,15 @@
         "id": "standalone.public_title"
       }
     ],
+    "path": "app/javascript/mastodon/features/standalone/community_timeline/index.json"
+  },
+  {
+    "descriptors": [
+      {
+        "defaultMessage": "A look inside...",
+        "id": "standalone.public_title"
+      }
+    ],
     "path": "app/javascript/mastodon/features/standalone/public_timeline/index.json"
   },
   {
@@ -1459,8 +1533,12 @@
         "id": "onboarding.page_one.federation"
       },
       {
-        "defaultMessage": "You are on {domain}, so your full handle is {handle}",
-        "id": "onboarding.page_one.handle"
+        "defaultMessage": "Your full handle",
+        "id": "onboarding.page_one.full_handle"
+      },
+      {
+        "defaultMessage": "This is what you would tell your friends to search for.",
+        "id": "onboarding.page_one.handle_hint"
       },
       {
         "defaultMessage": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
@@ -1536,6 +1614,10 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Close",
+        "id": "lightbox.close"
+      },
+      {
         "defaultMessage": "Additional comments",
         "id": "report.placeholder"
       },
@@ -1546,6 +1628,18 @@
       {
         "defaultMessage": "Report {target}",
         "id": "report.target"
+      },
+      {
+        "defaultMessage": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+        "id": "report.hint"
+      },
+      {
+        "defaultMessage": "The account is from another server. Send an anonymized copy of the report there as well?",
+        "id": "report.forward_hint"
+      },
+      {
+        "defaultMessage": "Forward to {target}",
+        "id": "report.forward"
       }
     ],
     "path": "app/javascript/mastodon/features/ui/components/report_modal.json"
@@ -1553,10 +1647,6 @@
   {
     "descriptors": [
       {
-        "defaultMessage": "Compose",
-        "id": "tabs_bar.compose"
-      },
-      {
         "defaultMessage": "Home",
         "id": "tabs_bar.home"
       },
@@ -1642,4 +1732,4 @@
     ],
     "path": "app/javascript/mastodon/features/video/index.json"
   }
-]
+]
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index ac25a57a2..e6e0b012b 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Block @{name}",
   "account.block_domain": "Hide everything from {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Edit profile",
   "account.follow": "Follow",
   "account.followers": "Followers",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
-  "account.posts": "Posts",
+  "account.muted": "Muted",
+  "account.posts": "Toots",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Report @{name}",
   "account.requested": "Awaiting approval. Click to cancel follow request",
   "account.share": "Share @{name}'s profile",
@@ -144,6 +148,7 @@
   "loading_indicator.label": "Loading...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
@@ -205,21 +210,30 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not post to public timelines",
   "privacy.unlisted.short": "Unlisted",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancel",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Additional comments",
   "report.submit": "Submit",
   "report.target": "Reporting {target}",
   "search.placeholder": "Search",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -235,6 +249,7 @@
   "status.mute_conversation": "Mute conversation",
   "status.open": "Expand this status",
   "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Boost",
   "status.reblogged_by": "{name} boosted",
   "status.reply": "Reply",
@@ -247,7 +262,6 @@
   "status.show_more": "Show more",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Compose",
   "tabs_bar.federated_timeline": "Federated",
   "tabs_bar.home": "Home",
   "tabs_bar.local_timeline": "Local",
@@ -256,6 +270,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Undo",
   "upload_progress.label": "Uploading...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index eab8c09a6..4f465bf40 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -1,232 +1,248 @@
 {
   "account.block": "Bloki @{name}",
-  "account.block_domain": "Kaŝi ĉion el {domain}",
-  "account.disclaimer_full": "La ĉi-subaj informoj povas ne plene reflekti la profilon de la uzanto.",
-  "account.edit_profile": "Redakti la profilon",
+  "account.block_domain": "Kaŝi ĉion de {domain}",
+  "account.blocked": "Blokita",
+  "account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.",
+  "account.domain_blocked": "Domajno kaŝita",
+  "account.edit_profile": "Redakti profilon",
   "account.follow": "Sekvi",
   "account.followers": "Sekvantoj",
   "account.follows": "Sekvatoj",
   "account.follows_you": "Sekvas vin",
-  "account.hide_reblogs": "Hide boosts from @{name}",
-  "account.media": "Sonbildaĵoj",
+  "account.hide_reblogs": "Kaŝi diskonigojn de @{name}",
+  "account.media": "Aŭdovidaĵoj",
   "account.mention": "Mencii @{name}",
-  "account.moved_to": "{name} has moved to:",
+  "account.moved_to": "{name} moviĝis al:",
   "account.mute": "Silentigi @{name}",
-  "account.mute_notifications": "Mute notifications from @{name}",
+  "account.mute_notifications": "Silentigi sciigojn el @{name}",
+  "account.muted": "Silentigita",
   "account.posts": "Mesaĝoj",
+  "account.posts_with_replies": "Mesaĝoj kun respondoj",
   "account.report": "Signali @{name}",
-  "account.requested": "Atendas aprobon",
+  "account.requested": "Atendo de aprobo. Alklaku por nuligi peton de sekvado",
   "account.share": "Diskonigi la profilon de @{name}",
-  "account.show_reblogs": "Show boosts from @{name}",
+  "account.show_reblogs": "Montri diskonigojn de @{name}",
   "account.unblock": "Malbloki @{name}",
   "account.unblock_domain": "Malkaŝi {domain}",
-  "account.unfollow": "Ne plus sekvi",
+  "account.unfollow": "Ne plu sekvi",
   "account.unmute": "Malsilentigi @{name}",
-  "account.unmute_notifications": "Unmute notifications from @{name}",
+  "account.unmute_notifications": "Malsilentigi sciigojn de @{name}",
   "account.view_full_profile": "Vidi plenan profilon",
-  "boost_modal.combo": "La proksiman fojon, premu {combo} por pasigi",
-  "bundle_column_error.body": "Io malfunkciis ŝargante tiun ĉi komponanton.",
+  "boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje",
+  "bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
   "bundle_column_error.retry": "Bonvolu reprovi",
   "bundle_column_error.title": "Reta eraro",
   "bundle_modal_error.close": "Fermi",
-  "bundle_modal_error.message": "Io malfunkciis ŝargante tiun ĉi komponanton.",
+  "bundle_modal_error.message": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
   "bundle_modal_error.retry": "Bonvolu reprovi",
   "column.blocks": "Blokitaj uzantoj",
   "column.community": "Loka tempolinio",
-  "column.favourites": "Favoritoj",
-  "column.follow_requests": "Abonpetoj",
+  "column.favourites": "Stelumoj",
+  "column.follow_requests": "Petoj de sekvado",
   "column.home": "Hejmo",
-  "column.lists": "Lists",
+  "column.lists": "Listoj",
   "column.mutes": "Silentigitaj uzantoj",
   "column.notifications": "Sciigoj",
-  "column.pins": "Alpinglitaj pepoj",
+  "column.pins": "Alpinglitaj mesaĝoj",
   "column.public": "Fratara tempolinio",
   "column_back_button.label": "Reveni",
   "column_header.hide_settings": "Kaŝi agordojn",
   "column_header.moveLeft_settings": "Movi kolumnon maldekstren",
   "column_header.moveRight_settings": "Movi kolumnon dekstren",
   "column_header.pin": "Alpingli",
-  "column_header.show_settings": "Malkaŝi agordojn",
+  "column_header.show_settings": "Montri agordojn",
   "column_header.unpin": "Depingli",
   "column_subheading.navigation": "Navigado",
-  "column_subheading.settings": "Agordoj",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
-  "compose_form.lock_disclaimer": "Via konta ne estas ŝlosita. Iu ajn povas sekvi vin por vidi viajn privatajn pepojn.",
+  "column_subheading.settings": "Agordado",
+  "compose_form.hashtag_warning": "Ĉi tiu mesaĝo ne estos listigita per ajna kradvorto. Nur publikaj mesaĝoj estas serĉeblaj per kradvortoj.",
+  "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn nur por sekvantoj.",
   "compose_form.lock_disclaimer.lock": "ŝlosita",
   "compose_form.placeholder": "Pri kio vi pensas?",
   "compose_form.publish": "Hup",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive": "Marki ke la enhavo estas tikla",
-  "compose_form.spoiler": "Kaŝi la tekston malantaŭ averto",
-  "compose_form.spoiler_placeholder": "Skribu tie vian averton",
-  "confirmation_modal.cancel": "Malfari",
+  "compose_form.sensitive": "Marki aŭdovidaĵon tikla",
+  "compose_form.spoiler": "Kaŝi tekston malantaŭ averto",
+  "compose_form.spoiler_placeholder": "Skribu vian averton ĉi tie",
+  "confirmation_modal.cancel": "Nuligi",
   "confirmations.block.confirm": "Bloki",
-  "confirmations.block.message": "Ĉu vi konfirmas la blokadon de {name}?",
-  "confirmations.delete.confirm": "Malaperigi",
-  "confirmations.delete.message": "Ĉu vi konfirmas la malaperigon de tiun pepon?",
-  "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
-  "confirmations.domain_block.confirm": "Kaŝi la tutan reton",
-  "confirmations.domain_block.message": "Ĉu vi vere, vere certas, ke vi volas bloki {domain} tute? Plej ofte, kelkaj celitaj blokadoj aŭ silentigoj estas sufiĉaj kaj preferindaj.",
+  "confirmations.block.message": "Ĉu vi certas, ke vi volas bloki {name}?",
+  "confirmations.delete.confirm": "Forigi",
+  "confirmations.delete.message": "Ĉu vi certas, ke vi volas forigi ĉi tiun mesaĝon?",
+  "confirmations.delete_list.confirm": "Forigi",
+  "confirmations.delete_list.message": "Ĉu vi certas, ke vi volas porĉiame forigi ĉi tiun liston?",
+  "confirmations.domain_block.confirm": "Kaŝi la tutan domajnon",
+  "confirmations.domain_block.message": "Ĉu vi vere, vere certas, ke vi volas tute bloki {domain}? Plej ofte, trafa blokado kaj silentigado sufiĉas kaj preferindas.",
   "confirmations.mute.confirm": "Silentigi",
-  "confirmations.mute.message": "Ĉu vi konfirmas la silentigon de {name}?",
+  "confirmations.mute.message": "Ĉu vi certas, ke vi volas silentigi {name}?",
   "confirmations.unfollow.confirm": "Ne plu sekvi",
-  "confirmations.unfollow.message": "Ĉu vi volas ĉesi sekvi {name}?",
-  "embed.instructions": "Enmetu tiun statkonigon ĉe vian retejon kopiante la ĉi-suban kodon.",
+  "confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?",
+  "embed.instructions": "Enkorpigu ĉi tiun mesaĝon en vian retejon per kopio de la suba kodo.",
   "embed.preview": "Ĝi aperos tiel:",
-  "emoji_button.activity": "Aktivecoj",
-  "emoji_button.custom": "Personaj",
+  "emoji_button.activity": "Agadoj",
+  "emoji_button.custom": "Propraj",
   "emoji_button.flags": "Flagoj",
   "emoji_button.food": "Manĝi kaj trinki",
-  "emoji_button.label": "Enmeti mieneton",
+  "emoji_button.label": "Enmeti emoĝion",
   "emoji_button.nature": "Naturo",
-  "emoji_button.not_found": "Neniuj mienetoj!! (╯°□°)╯︵ ┻━┻",
-  "emoji_button.objects": "Objektoj",
+  "emoji_button.not_found": "Neniu emoĝio!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Aĵoj",
   "emoji_button.people": "Homoj",
   "emoji_button.recent": "Ofte uzataj",
   "emoji_button.search": "Serĉo…",
-  "emoji_button.search_results": "Rezultatoj de serĉo",
+  "emoji_button.search_results": "Serĉaj rezultoj",
   "emoji_button.symbols": "Simboloj",
-  "emoji_button.travel": "Vojaĝoj & lokoj",
+  "emoji_button.travel": "Vojaĝoj kaj lokoj",
   "empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
-  "empty_column.hashtag": "Ĝise, neniu enhavo estas asociita kun tiu kradvorto.",
+  "empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
   "empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
   "empty_column.home.public_timeline": "la publika tempolinio",
-  "empty_column.list": "There is nothing in this list yet.",
-  "empty_column.notifications": "Vi dume ne havas sciigojn. Interagi kun aliajn uzantojn por komenci la konversacion.",
-  "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj instancoj por plenigi la publikan tempolinion.",
-  "follow_request.authorize": "Akcepti",
+  "empty_column.list": "Ankoraŭ estas nenio en ĉi tiu listo. Kiam membroj de ĉi tiu listo afiŝos novajn mesaĝojn, ili aperos ĉi tie.",
+  "empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
+  "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj nodoj por plenigi la publikan tempolinion",
+  "follow_request.authorize": "Rajtigi",
   "follow_request.reject": "Rifuzi",
   "getting_started.appsshort": "Aplikaĵoj",
   "getting_started.faq": "Oftaj demandoj",
   "getting_started.heading": "Por komenci",
-  "getting_started.open_source_notice": "Mastodono estas malfermkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
+  "getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
   "getting_started.userguide": "Gvidilo de uzo",
   "home.column_settings.advanced": "Precizaj agordoj",
   "home.column_settings.basic": "Bazaj agordoj",
-  "home.column_settings.filter_regex": "Forfiltri per regulesprimo",
+  "home.column_settings.filter_regex": "Filtri per regulesprimoj",
   "home.column_settings.show_reblogs": "Montri diskonigojn",
   "home.column_settings.show_replies": "Montri respondojn",
-  "home.settings": "Agordoj de la kolumno",
-  "keyboard_shortcuts.back": "to navigate back",
-  "keyboard_shortcuts.boost": "to boost",
-  "keyboard_shortcuts.column": "to focus a status in one of the columns",
-  "keyboard_shortcuts.compose": "to focus the compose textarea",
-  "keyboard_shortcuts.description": "Description",
-  "keyboard_shortcuts.down": "to move down in the list",
-  "keyboard_shortcuts.enter": "to open status",
-  "keyboard_shortcuts.favourite": "to favourite",
-  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.hotkey": "Hotkey",
-  "keyboard_shortcuts.legend": "to display this legend",
-  "keyboard_shortcuts.mention": "to mention author",
-  "keyboard_shortcuts.reply": "to reply",
-  "keyboard_shortcuts.search": "to focus search",
-  "keyboard_shortcuts.toot": "to start a brand new toot",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "to move up in the list",
+  "home.settings": "Kolumnaj agordoj",
+  "keyboard_shortcuts.back": "por reveni",
+  "keyboard_shortcuts.boost": "por diskonigi",
+  "keyboard_shortcuts.column": "por fokusigi mesaĝon en unu el la kolumnoj",
+  "keyboard_shortcuts.compose": "por fokusigi la tekstujon",
+  "keyboard_shortcuts.description": "Priskribo",
+  "keyboard_shortcuts.down": "por iri suben en la listo",
+  "keyboard_shortcuts.enter": "por malfermi mesaĝon",
+  "keyboard_shortcuts.favourite": "por stelumi",
+  "keyboard_shortcuts.heading": "Klavaraj mallongigoj",
+  "keyboard_shortcuts.hotkey": "Rapidklavo",
+  "keyboard_shortcuts.legend": "por montri ĉi tiun noton",
+  "keyboard_shortcuts.mention": "por mencii la aŭtoron",
+  "keyboard_shortcuts.reply": "por respondi",
+  "keyboard_shortcuts.search": "por fokusigi la serĉilon",
+  "keyboard_shortcuts.toot": "por komenci tute novan mesaĝon",
+  "keyboard_shortcuts.unfocus": "por malfokusigi la tekstujon aŭ la serĉilon",
+  "keyboard_shortcuts.up": "por iri supren en la listo",
   "lightbox.close": "Fermi",
-  "lightbox.next": "Malantaŭa",
+  "lightbox.next": "Sekva",
   "lightbox.previous": "Antaŭa",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
-  "lists.delete": "Delete list",
-  "lists.edit": "Edit list",
-  "lists.new.create": "Add list",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Your lists",
-  "loading_indicator.label": "Ŝarganta…",
-  "media_gallery.toggle_visible": "Baskuli videblecon",
+  "lists.account.add": "Aldoni al la listo",
+  "lists.account.remove": "Forigi de la listo",
+  "lists.delete": "Forigi la liston",
+  "lists.edit": "Redakti la liston",
+  "lists.new.create": "Aldoni liston",
+  "lists.new.title_placeholder": "Titolo de la nova listo",
+  "lists.search": "Serĉi inter la homoj, kiujn vi sekvas",
+  "lists.subheading": "Viaj listoj",
+  "loading_indicator.label": "Ŝargado…",
+  "media_gallery.toggle_visible": "Baskuligi videblecon",
   "missing_indicator.label": "Ne trovita",
-  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "missing_indicator.sublabel": "Ĉi tiu rimedo ne estis trovita",
+  "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.edit_profile": "Redakti la profilon",
-  "navigation_bar.favourites": "Favoritaj",
-  "navigation_bar.follow_requests": "Abonpetoj",
-  "navigation_bar.info": "Plia informo",
-  "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
-  "navigation_bar.lists": "Lists",
+  "navigation_bar.edit_profile": "Redakti profilon",
+  "navigation_bar.favourites": "Stelumoj",
+  "navigation_bar.follow_requests": "Petoj de sekvado",
+  "navigation_bar.info": "Pri ĉi tiu nodo",
+  "navigation_bar.keyboard_shortcuts": "Klavaraj mallongigoj",
+  "navigation_bar.lists": "Listoj",
   "navigation_bar.logout": "Elsaluti",
   "navigation_bar.mutes": "Silentigitaj uzantoj",
-  "navigation_bar.pins": "Alpinglitaj pepoj",
+  "navigation_bar.pins": "Alpinglitaj mesaĝoj",
   "navigation_bar.preferences": "Preferoj",
   "navigation_bar.public_timeline": "Fratara tempolinio",
-  "notification.favourite": "{name} favoris vian mesaĝon",
-  "notification.follow": "{name} sekvis vin",
+  "notification.favourite": "{name} stelumis vian mesaĝon",
+  "notification.follow": "{name} eksekvis vin",
   "notification.mention": "{name} menciis vin",
   "notification.reblog": "{name} diskonigis vian mesaĝon",
-  "notifications.clear": "Forviŝi la sciigojn",
-  "notifications.clear_confirmation": "Ĉu vi certe volas malaperigi ĉiujn viajn sciigojn?",
-  "notifications.column_settings.alert": "Retumilaj atentigoj",
-  "notifications.column_settings.favourite": "Favoritoj:",
+  "notifications.clear": "Forviŝi sciigojn",
+  "notifications.clear_confirmation": "Ĉu vi certas, ke vi volas porĉiame forviŝi ĉiujn viajn sciigojn?",
+  "notifications.column_settings.alert": "Retumilaj sciigoj",
+  "notifications.column_settings.favourite": "Stelumoj:",
   "notifications.column_settings.follow": "Novaj sekvantoj:",
   "notifications.column_settings.mention": "Mencioj:",
   "notifications.column_settings.push": "Puŝsciigoj",
-  "notifications.column_settings.push_meta": "Tiu ĉi aparato",
+  "notifications.column_settings.push_meta": "Ĉi tiu aparato",
   "notifications.column_settings.reblog": "Diskonigoj:",
-  "notifications.column_settings.show": "Montri en kolono",
+  "notifications.column_settings.show": "Montri en kolumno",
   "notifications.column_settings.sound": "Eligi sonon",
   "onboarding.done": "Farita",
-  "onboarding.next": "Malantaŭa",
-  "onboarding.page_five.public_timelines": "La loka tempolinio enhavas mesaĝojn de ĉiuj ĉe {domain}. La federacia tempolinio enhavas ĉiujn mesaĝojn de uzantoj, kiujn iu ĉe {domain} sekvas. Ambaŭ tre utilas por trovi novajn kunparolantojn.",
-  "onboarding.page_four.home": "La hejma tempolinio enhavas la mesaĝojn de ĉiuj uzantoj, kiuj vi sekvas.",
-  "onboarding.page_four.notifications": "La sciiga kolumno informas vin kiam iu interagas kun vi.",
-  "onboarding.page_one.federation": "Mastodono estas reto de nedependaj serviloj, unuiĝintaj por krei pligrandan socian retejon. Ni nomas tiujn servilojn instancoj.",
-  "onboarding.page_one.handle": "Vi estas ĉe {domain}, unu el la multaj instancoj de Mastodono. Via kompleta uznomo do estas {handle}",
-  "onboarding.page_one.welcome": "Bonvenon al Mastodono!",
-  "onboarding.page_six.admin": "Via instancestro estas {admin}.",
-  "onboarding.page_six.almost_done": "Estas preskaŭ finita…",
-  "onboarding.page_six.appetoot": "Bonan a‘pepi’ton!",
-  "onboarding.page_six.apps_available": "{apps} estas elŝuteblaj por iOS, Androido kaj alioj. Kaj nun… bonan a‘pepi’ton!",
-  "onboarding.page_six.github": "Mastodono estas libera, senpaga kaj malfermkoda programaro. Vi povas signali cimojn, proponi funkciojn aŭ kontribui al gîa kreskado ĉe {github}.",
-  "onboarding.page_six.guidelines": "komunreguloj",
-  "onboarding.page_six.read_guidelines": "Ni petas vin: ne forgesu legi la {guidelines}n de {domain}!",
-  "onboarding.page_six.various_app": "telefon-aplikaĵoj",
-  "onboarding.page_three.profile": "Redaktu vian profilon por ŝanĝi vian avataron, priskribon kaj vian nomon. Vi tie trovos ankoraŭ aliajn agordojn.",
-  "onboarding.page_three.search": "Uzu la serĉokampo por trovi uzantojn kaj esplori kradvortojn tiel ke {illustration} kaj {introductions}. Por trovi iun, kiu ne estas ĉe ĉi tiu instanco, uzu ĝian kompletan uznomon.",
-  "onboarding.page_two.compose": "Skribu pepojn en la verkkolumno. Vi povas aldoni bildojn, ŝanĝi la agordojn de privateco kaj aldoni tiklavertojn (« content warning ») dank' al la piktogramoj malsupre.",
-  "onboarding.skip": "Pasigi",
-  "privacy.change": "Alĝustigi la privateco de la mesaĝo",
-  "privacy.direct.long": "Vidigi nur al la menciitaj personoj",
+  "onboarding.next": "Sekva",
+  "onboarding.page_five.public_timelines": "La loka tempolinio montras publikajn mesaĝojn de ĉiuj en {domain}. La fratara tempolinio montras publikajn mesaĝojn de ĉiuj, kiuj estas sekvataj de homoj en {domain}. Tio estas la publikaj tempolinioj, kio estas bona maniero por malkovri novajn homojn.",
+  "onboarding.page_four.home": "La hejma tempolinio montras mesaĝojn de ĉiuj uzantoj, kiujn vi sekvas.",
+  "onboarding.page_four.notifications": "La sciiga kolumno montras kiam iu interagas kun vi.",
+  "onboarding.page_one.federation": "Mastodon estas reto de sendependaj serviloj, unuiĝintaj por krei pligrandan socian reton. Ni nomas tiujn servilojn nodoj.",
+  "onboarding.page_one.full_handle": "Via kompleta uzantnomo",
+  "onboarding.page_one.handle_hint": "Jen kion vi petus al viaj amikoj serĉi.",
+  "onboarding.page_one.welcome": "Bonvenon en Mastodon!",
+  "onboarding.page_six.admin": "Via noda administranto estas {admin}.",
+  "onboarding.page_six.almost_done": "Preskaŭ finita…",
+  "onboarding.page_six.appetoot": "Saĝan mesaĝadon!",
+  "onboarding.page_six.apps_available": "{apps} estas disponeblaj por iOS, Android kaj aliaj platformoj.",
+  "onboarding.page_six.github": "Mastodon estas libera, senpaga kaj malfermitkoda programo. Vi povas raporti cimojn, proponi funkciojn aŭ kontribui al la kodo en {github}.",
+  "onboarding.page_six.guidelines": "komunumaj gvidlinioj",
+  "onboarding.page_six.read_guidelines": "Bonvolu atenti pri la {guidelines} de {domain}!",
+  "onboarding.page_six.various_app": "telefonaj aplikaĵoj",
+  "onboarding.page_three.profile": "Redaktu vian profilon por ŝanĝi vian profilbildon, priskribon kaj nomon. Vi ankaŭ trovos tie aliajn agordojn.",
+  "onboarding.page_three.search": "Uzu la serĉilon por trovi uzantojn kaj esplori kradvortojn, tiel {illustration} kaj {introductions}. Por trovi iun, kiu ne estas en ĉi tiu nodo, uzu ties kompletan uzantnomon.",
+  "onboarding.page_two.compose": "Skribu mesaĝojn en la skriba kolumno. Vi povas alŝuti bildojn, ŝanĝi privatecajn agordojn, kaj aldoni avertojn pri la enhavo per la subaj bildetoj.",
+  "onboarding.skip": "Preterpasi",
+  "privacy.change": "Agordi mesaĝan privatecon",
+  "privacy.direct.long": "Afiŝi nur al menciitaj uzantoj",
   "privacy.direct.short": "Rekta",
-  "privacy.private.long": "Vidigi nur al viaj sekvantoj",
-  "privacy.private.short": "Nursekvanta",
-  "privacy.public.long": "Vidigi en publikaj tempolinioj",
+  "privacy.private.long": "Afiŝi nur al sekvantoj",
+  "privacy.private.short": "Nur por sekvantoj",
+  "privacy.public.long": "Afiŝi en publikaj tempolinioj",
   "privacy.public.short": "Publika",
-  "privacy.unlisted.long": "Ne vidigi en publikaj tempolinioj",
+  "privacy.unlisted.long": "Ne afiŝi en publikaj tempolinioj",
   "privacy.unlisted.short": "Nelistigita",
+  "regeneration_indicator.label": "Ŝargado…",
+  "regeneration_indicator.sublabel": "Via hejma fluo pretiĝas!",
   "relative_time.days": "{number}t",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "nun",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
-  "reply_indicator.cancel": "Malfari",
+  "reply_indicator.cancel": "Nuligi",
+  "report.forward": "Plusendi al {target}",
+  "report.forward_hint": "La konto estas en alia servilo. Ĉu sendi sennomigitan kopion de la signalo ankaŭ tien?",
+  "report.hint": "La signalo estos sendita al la kontrolantoj de via nodo. Vi povas doni klarigon pri kial vi signalas ĉi tiun konton sube:",
   "report.placeholder": "Pliaj komentoj",
   "report.submit": "Sendi",
-  "report.target": "Signalaĵo",
+  "report.target": "Signali {target}",
   "search.placeholder": "Serĉi",
   "search_popout.search_format": "Detala serĉo",
+  "search_popout.tips.full_text": "Simplaj tekstoj montras la mesaĝojn, kiujn vi skribis, stelumis, diskonigis, aŭ en kiuj vi estis menciita, sed ankaŭ kongruajn uzantnomojn, montratajn nomojn, kaj kradvortojn.",
   "search_popout.tips.hashtag": "kradvorto",
-  "search_popout.tips.status": "statkonigo",
-  "search_popout.tips.text": "Simpla teksto eligas la kongruajn afiŝnomojn, uznomojn kaj kradvortojn.",
+  "search_popout.tips.status": "mesaĝoj",
+  "search_popout.tips.text": "Simpla teksto montras la kongruajn afiŝitajn nomojn, uzantnomojn kaj kradvortojn",
   "search_popout.tips.user": "uzanto",
-  "search_results.total": "{count, number} {count, plural, one {rezultato} other {rezultatoj}}",
-  "standalone.public_title": "Rigardeti…",
-  "status.block": "Block @{name}",
-  "status.cannot_reblog": "Tiun publikaĵon oni ne povas diskonigi",
+  "search_results.accounts": "Homoj",
+  "search_results.hashtags": "Kradvortoj",
+  "search_results.statuses": "Mesaĝoj",
+  "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}",
+  "standalone.public_title": "Enrigardo…",
+  "status.block": "Bloki @{name}",
+  "status.cannot_reblog": "Ĉi tiu mesaĝo ne diskonigeblas",
   "status.delete": "Forigi",
-  "status.embed": "Enmeti",
-  "status.favourite": "Favori",
-  "status.load_more": "Ŝargi plie",
-  "status.media_hidden": "Sonbildaĵo kaŝita",
+  "status.embed": "Enkorpigi",
+  "status.favourite": "Stelumi",
+  "status.load_more": "Ŝargi pli",
+  "status.media_hidden": "Aŭdovidaĵo kaŝita",
   "status.mention": "Mencii @{name}",
   "status.more": "Pli",
-  "status.mute": "Mute @{name}",
+  "status.mute": "Silentigi @{name}",
   "status.mute_conversation": "Silentigi konversacion",
-  "status.open": "Disfaldi statkonigon",
-  "status.pin": "Pingli al la profilo",
+  "status.open": "Grandigi ĉi tiun mesaĝon",
+  "status.pin": "Alpingli en la profilo",
+  "status.pinned": "Alpinglita mesaĝo",
   "status.reblog": "Diskonigi",
   "status.reblogged_by": "{name} diskonigis",
   "status.reply": "Respondi",
@@ -235,28 +251,28 @@
   "status.sensitive_toggle": "Alklaki por vidi",
   "status.sensitive_warning": "Tikla enhavo",
   "status.share": "Diskonigi",
-  "status.show_less": "Refaldi",
-  "status.show_more": "Disfaldi",
+  "status.show_less": "Malgrandigi",
+  "status.show_more": "Grandigi",
   "status.unmute_conversation": "Malsilentigi konversacion",
   "status.unpin": "Depingli de profilo",
-  "tabs_bar.compose": "Ekskribi",
-  "tabs_bar.federated_timeline": "Federacia tempolinio",
+  "tabs_bar.federated_timeline": "Fratara tempolinio",
   "tabs_bar.home": "Hejmo",
   "tabs_bar.local_timeline": "Loka tempolinio",
   "tabs_bar.notifications": "Sciigoj",
-  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
-  "upload_area.title": "Algliti por alŝuti",
-  "upload_button.label": "Aldoni sonbildaĵon",
-  "upload_form.description": "Priskribi por la misvidantaj",
+  "ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.",
+  "upload_area.title": "Altreni kaj lasi por alŝuti",
+  "upload_button.label": "Aldoni aŭdovidaĵon",
+  "upload_form.description": "Priskribi por misvidantaj homoj",
+  "upload_form.focus": "Stuci",
   "upload_form.undo": "Malfari",
-  "upload_progress.label": "Alŝutanta…",
+  "upload_progress.label": "Alŝutado…",
   "video.close": "Fermi videon",
-  "video.exit_fullscreen": "Eliri el plenekrano",
-  "video.expand": "Vastigi videon",
-  "video.fullscreen": "Igi plenekrane",
+  "video.exit_fullscreen": "Eksigi plenekrana",
+  "video.expand": "Grandigi videon",
+  "video.fullscreen": "Igi plenekrana",
   "video.hide": "Kaŝi videon",
   "video.mute": "Silentigi",
   "video.pause": "Paŭzi",
-  "video.play": "Legi",
+  "video.play": "Ekigi",
   "video.unmute": "Malsilentigi"
 }
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 8a8110b1e..d172dff1c 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Bloquear",
   "account.block_domain": "Ocultar todo de {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "La siguiente información del usuario puede estar incompleta.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Editar perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} se ha mudado a:",
   "account.mute": "Silenciar a @{name}",
   "account.mute_notifications": "Silenciar notificaciones de @{name}",
+  "account.muted": "Muted",
   "account.posts": "Publicaciones",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Reportar a @{name}",
   "account.requested": "Esperando aprobación",
   "account.share": "Compartir el perfil de @{name}",
@@ -36,7 +40,7 @@
   "column.favourites": "Favoritos",
   "column.follow_requests": "Solicitudes de seguimiento",
   "column.home": "Inicio",
-  "column.lists": "Lists",
+  "column.lists": "Listas",
   "column.mutes": "Usuarios silenciados",
   "column.notifications": "Notificaciones",
   "column.pins": "Toot fijado",
@@ -92,7 +96,7 @@
   "empty_column.hashtag": "No hay nada en este hashtag aún.",
   "empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.",
   "empty_column.home.public_timeline": "la línea de tiempo pública",
-  "empty_column.list": "No hay nada en esta lista aún.",
+  "empty_column.list": "No hay nada en esta lista aún. Cuando miembros de esta lista publiquen nuevos estatus, estos aparecerán qui.",
   "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
   "empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo",
   "follow_request.authorize": "Autorizar",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Cargando…",
   "media_gallery.toggle_visible": "Cambiar visibilidad",
   "missing_indicator.label": "No encontrado",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Ocultar notificaciones de este usuario?",
   "navigation_bar.blocks": "Usuarios bloqueados",
   "navigation_bar.community_timeline": "Historia local",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "La línea de tiempo principal muestra toots de gente que sigues.",
   "onboarding.page_four.notifications": "Las notificaciones se muestran cuando alguien interactúa contigo.",
   "onboarding.page_one.federation": "Mastodon es una red de servidores federados que conforman una red social aún más grande. Llamamos a estos servidores instancias.",
-  "onboarding.page_one.handle": "Estás en {domain}, así que tu nombre de usuario completo es {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "¡Bienvenido a Mastodon!",
   "onboarding.page_six.admin": "El administrador de tu instancia es {admin}.",
   "onboarding.page_six.almost_done": "Ya casi…",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Público",
   "privacy.unlisted.long": "No mostrar en la historia federada",
   "privacy.unlisted.short": "Sin federar",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "ahora",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancelar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Comentarios adicionales",
   "report.submit": "Publicar",
   "report.target": "Reportando",
   "search.placeholder": "Buscar",
   "search_popout.search_format": "Formato de búsqueda avanzada",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "etiqueta",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag",
   "search_popout.tips.user": "usuario",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Un pequeño vistazo...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Silenciar conversación",
   "status.open": "Expandir estado",
   "status.pin": "Fijar",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Retootear",
   "status.reblogged_by": "Retooteado por {name}",
   "status.reply": "Responder",
@@ -239,7 +255,6 @@
   "status.show_more": "Mostrar más",
   "status.unmute_conversation": "Dejar de silenciar conversación",
   "status.unpin": "Dejar de fijar",
-  "tabs_bar.compose": "Redactar",
   "tabs_bar.federated_timeline": "Federado",
   "tabs_bar.home": "Inicio",
   "tabs_bar.local_timeline": "Local",
@@ -248,6 +263,7 @@
   "upload_area.title": "Arrastra y suelta para subir",
   "upload_button.label": "Subir multimedia",
   "upload_form.description": "Describir para los usuarios con dificultad visual",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Deshacer",
   "upload_progress.label": "Subiendo…",
   "video.close": "Cerrar video",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 8c52ffdb4..b5b81bff9 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -1,7 +1,9 @@
 {
   "account.block": "مسدودسازی @{name}",
   "account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "ویرایش نمایه",
   "account.follow": "پی بگیرید",
   "account.followers": "پیگیران",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} منتقل شده است به:",
   "account.mute": "بی‌صدا کردن @{name}",
   "account.mute_notifications": "بی‌صداکردن اعلان‌ها از طرف @{name}",
+  "account.muted": "Muted",
   "account.posts": "نوشته‌ها",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "گزارش @{name}",
   "account.requested": "در انتظار پذیرش",
   "account.share": "هم‌رسانی نمایهٔ @{name}",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "بارگیری...",
   "media_gallery.toggle_visible": "تغییر پیدایی",
   "missing_indicator.label": "پیدا نشد",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "اعلان‌های این کاربر پنهان شود؟",
   "navigation_bar.blocks": "کاربران مسدودشده",
   "navigation_bar.community_timeline": "نوشته‌های محلی",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "ستون «خانه» نوشته‌های کسانی را نشان می‌دهد که شما پی می‌گیرید.",
   "onboarding.page_four.notifications": "ستون «اعلان‌ها» ارتباط‌های شما با دیگران را نشان می‌دهد.",
   "onboarding.page_one.federation": "ماستدون شبکه‌ای از سرورهای مستقل است که با پیوستن به یکدیگر یک شبکهٔ اجتماعی بزرگ را تشکیل می‌دهند.",
-  "onboarding.page_one.handle": "شما روی سرور {domain} هستید، بنابراین شناسهٔ کامل شما {handle} است",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "به ماستدون خوش آمدید!",
   "onboarding.page_six.admin": "نشانی مسئول سرور شما {admin} است.",
   "onboarding.page_six.almost_done": "الان تقریباً آماده‌اید...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "عمومی",
   "privacy.unlisted.long": "عمومی، ولی فهرست نکن",
   "privacy.unlisted.short": "فهرست‌نشده",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "الان",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "لغو",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "توضیح اضافه",
   "report.submit": "بفرست",
   "report.target": "گزارش‌دادن",
   "search.placeholder": "جستجو",
   "search_popout.search_format": "راهنمای جستجوی پیشرفته",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "هشتگ",
   "search_popout.tips.status": "نوشته",
   "search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و هشتگ‌ها",
   "search_popout.tips.user": "کاربر",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
   "standalone.public_title": "نگاهی به کاربران این سرور...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "بی‌صداکردن گفتگو",
   "status.open": "این نوشته را باز کن",
   "status.pin": "نوشتهٔ ثابت نمایه",
+  "status.pinned": "Pinned toot",
   "status.reblog": "بازبوقیدن",
   "status.reblogged_by": "‫{name}‬ بازبوقید",
   "status.reply": "پاسخ",
@@ -239,7 +255,6 @@
   "status.show_more": "نمایش",
   "status.unmute_conversation": "باصداکردن گفتگو",
   "status.unpin": "برداشتن نوشتهٔ ثابت نمایه",
-  "tabs_bar.compose": "بنویسید",
   "tabs_bar.federated_timeline": "همگانی",
   "tabs_bar.home": "خانه",
   "tabs_bar.local_timeline": "محلی",
@@ -248,6 +263,7 @@
   "upload_area.title": "برای بارگذاری به این‌جا بکشید",
   "upload_button.label": "افزودن تصویر",
   "upload_form.description": "نوشتهٔ توضیحی برای کم‌بینایان و نابینایان",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "واگردانی",
   "upload_progress.label": "بارگذاری...",
   "video.close": "بستن ویدیو",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index 74ab699c4..aa97aae84 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -1,262 +1,278 @@
 {
   "account.block": "Estä @{name}",
-  "account.block_domain": "Hide everything from {domain}",
-  "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}",
+  "account.blocked": "Blocked",
+  "account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Muokkaa",
   "account.follow": "Seuraa",
   "account.followers": "Seuraajia",
   "account.follows": "Seuraa",
   "account.follows_you": "Seuraa sinua",
-  "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.hide_reblogs": "Piilota buustaukset käyttäjältä @{name}",
   "account.media": "Media",
   "account.mention": "Mainitse @{name}",
-  "account.moved_to": "{name} has moved to:",
-  "account.mute": "Mute @{name}",
-  "account.mute_notifications": "Mute notifications from @{name}",
-  "account.posts": "Postit",
+  "account.moved_to": "{name} on muuttanut instanssiin:",
+  "account.mute": "Mykistä @{name}",
+  "account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}",
+  "account.muted": "Muted",
+  "account.posts": "Töötit",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Report @{name}",
-  "account.requested": "Odottaa hyväksyntää",
-  "account.share": "Share @{name}'s profile",
-  "account.show_reblogs": "Show boosts from @{name}",
+  "account.requested": "Odottaa hyväksyntää. Klikkaa peruuttaaksesi seurauspyynnön",
+  "account.share": "Jaa käyttäjän @{name} profiili",
+  "account.show_reblogs": "Näytä boostaukset käyttäjältä @{name}",
   "account.unblock": "Salli @{name}",
-  "account.unblock_domain": "Unhide {domain}",
-  "account.unfollow": "Lopeta seuraaminen",
-  "account.unmute": "Unmute @{name}",
-  "account.unmute_notifications": "Unmute notifications from @{name}",
-  "account.view_full_profile": "View full profile",
-  "boost_modal.combo": "You can press {combo} to skip this next time",
-  "bundle_column_error.body": "Something went wrong while loading this component.",
-  "bundle_column_error.retry": "Try again",
+  "account.unblock_domain": "Näytä {domain}",
+  "account.unfollow": "Lakkaa seuraamasta",
+  "account.unmute": "Poista mykistys käyttäjältä @{name}",
+  "account.unmute_notifications": "Poista mykistys käyttäjän @{name} ilmoituksilta",
+  "account.view_full_profile": "Näytä koko profiili",
+  "boost_modal.combo": "Voit painaa näppäimiä {combo} ohittaaksesi tämän ensi kerralla",
+  "bundle_column_error.body": "Jokin meni vikaan tätä komponenttia ladatessa.",
+  "bundle_column_error.retry": "Yritä uudestaan",
   "bundle_column_error.title": "Network error",
-  "bundle_modal_error.close": "Close",
-  "bundle_modal_error.message": "Something went wrong while loading this component.",
-  "bundle_modal_error.retry": "Try again",
-  "column.blocks": "Blocked users",
+  "bundle_modal_error.close": "Sulje",
+  "bundle_modal_error.message": "Jokin meni vikaan tätä komponenttia ladatessa.",
+  "bundle_modal_error.retry": "Yritä uudestaan",
+  "column.blocks": "Estetyt käyttäjät",
   "column.community": "Paikallinen aikajana",
-  "column.favourites": "Favourites",
-  "column.follow_requests": "Follow requests",
+  "column.favourites": "Suosikit",
+  "column.follow_requests": "Seurauspyynnöt",
   "column.home": "Koti",
-  "column.lists": "Lists",
-  "column.mutes": "Muted users",
+  "column.lists": "Listat",
+  "column.mutes": "Mykistetyt käyttäjät",
   "column.notifications": "Ilmoitukset",
   "column.pins": "Pinned toot",
   "column.public": "Yleinen aikajana",
   "column_back_button.label": "Takaisin",
-  "column_header.hide_settings": "Hide settings",
-  "column_header.moveLeft_settings": "Move column to the left",
-  "column_header.moveRight_settings": "Move column to the right",
-  "column_header.pin": "Pin",
-  "column_header.show_settings": "Show settings",
-  "column_header.unpin": "Unpin",
-  "column_subheading.navigation": "Navigation",
-  "column_subheading.settings": "Settings",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
-  "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
-  "compose_form.lock_disclaimer.lock": "locked",
+  "column_header.hide_settings": "Piilota asetukset",
+  "column_header.moveLeft_settings": "Siirrä saraketta vasemmalle",
+  "column_header.moveRight_settings": "Siirrä saraketta oikealle",
+  "column_header.pin": "Kiinnitä",
+  "column_header.show_settings": "Näytä asetukset",
+  "column_header.unpin": "Poista kiinnitys",
+  "column_subheading.navigation": "Navigaatio",
+  "column_subheading.settings": "Asetukset",
+  "compose_form.hashtag_warning": "Tämä töötti ei tule näkymään hashtag-hauissa, koska se ei näy julkisilla aikajanoilla. Vain julkisia tööttejä voi hakea hashtageilla.",
+  "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille -postauksesi.",
+  "compose_form.lock_disclaimer.lock": "lukittu",
   "compose_form.placeholder": "Mitä sinulla on mielessä?",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive": "Merkitse media herkäksi",
   "compose_form.spoiler": "Piiloita teksti varoituksen taakse",
   "compose_form.spoiler_placeholder": "Content warning",
-  "confirmation_modal.cancel": "Cancel",
-  "confirmations.block.confirm": "Block",
-  "confirmations.block.message": "Are you sure you want to block {name}?",
+  "confirmation_modal.cancel": "Peruuta",
+  "confirmations.block.confirm": "Estä",
+  "confirmations.block.message": "Oletko varma, että haluat estää käyttäjän {name}?",
   "confirmations.delete.confirm": "Delete",
-  "confirmations.delete.message": "Are you sure you want to delete this status?",
+  "confirmations.delete.message": "Oletko varma, että haluat poistaa tämän statuspäivityksen?",
   "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
-  "confirmations.domain_block.confirm": "Hide entire domain",
-  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
-  "confirmations.mute.confirm": "Mute",
-  "confirmations.mute.message": "Are you sure you want to mute {name}?",
-  "confirmations.unfollow.confirm": "Unfollow",
-  "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
-  "embed.instructions": "Embed this status on your website by copying the code below.",
-  "embed.preview": "Here is what it will look like:",
-  "emoji_button.activity": "Activity",
-  "emoji_button.custom": "Custom",
-  "emoji_button.flags": "Flags",
-  "emoji_button.food": "Food & Drink",
-  "emoji_button.label": "Insert emoji",
-  "emoji_button.nature": "Nature",
-  "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
-  "emoji_button.objects": "Objects",
-  "emoji_button.people": "People",
-  "emoji_button.recent": "Frequently used",
-  "emoji_button.search": "Search...",
-  "emoji_button.search_results": "Search results",
-  "emoji_button.symbols": "Symbols",
-  "emoji_button.travel": "Travel & Places",
-  "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
-  "empty_column.hashtag": "There is nothing in this hashtag yet.",
-  "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
-  "empty_column.home.public_timeline": "the public timeline",
-  "empty_column.list": "There is nothing in this list yet.",
-  "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
-  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
-  "follow_request.authorize": "Authorize",
-  "follow_request.reject": "Reject",
-  "getting_started.appsshort": "Apps",
+  "confirmations.delete_list.message": "Oletko varma, että haluat poistaa tämän listan pysyvästi?",
+  "confirmations.domain_block.confirm": "Piilota koko verkko-osoite",
+  "confirmations.domain_block.message": "Oletko aivan oikeasti varma että haluat estää koko verkko-osoitteen {domain}? Useimmissa tapauksissa muutamat kohdistetut estot ja mykistykset ovat riittäviä ja suositeltavampia.",
+  "confirmations.mute.confirm": "Mykistä",
+  "confirmations.mute.message": "Oletko varma että haluat mykistää käyttäjän {name}?",
+  "confirmations.unfollow.confirm": "Lakkaa seuraamasta",
+  "confirmations.unfollow.message": "Oletko varma, että haluat lakata seuraamasta käyttäjää {name}?",
+  "embed.instructions": "Upota tämä statuspäivitys sivullesi kopioimalla alla oleva koodi.",
+  "embed.preview": "Tältä se tulee näyttämään:",
+  "emoji_button.activity": "Aktiviteetit",
+  "emoji_button.custom": "Mukautetut",
+  "emoji_button.flags": "Liput",
+  "emoji_button.food": "Ruoka ja juoma",
+  "emoji_button.label": "Lisää emoji",
+  "emoji_button.nature": "Luonto",
+  "emoji_button.not_found": "Ei emojeja!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Objektit",
+  "emoji_button.people": "Ihmiset",
+  "emoji_button.recent": "Usein käytetyt",
+  "emoji_button.search": "Etsi...",
+  "emoji_button.search_results": "Hakutulokset",
+  "emoji_button.symbols": "Symbolit",
+  "emoji_button.travel": "Matkailu",
+  "empty_column.community": "Paikallinen aikajana on tyhjä. Kirjoita jotain julkista saadaksesi pyörät pyörimään!",
+  "empty_column.hashtag": "Tässä hashtagissa ei ole vielä mitään.",
+  "empty_column.home": "Kotiaikajanasi on tyhjä! Käy vierailemassa {public}ssa tai käytä hakutoimintoa aloittaaksesi ja tavataksesi muita käyttäjiä.",
+  "empty_column.home.public_timeline": "yleinen aikajana",
+  "empty_column.list": "Tämä lista on vielä tyhjä. Kun listan jäsenet julkaisevat statuspäivityksiä, ne näkyvät tässä.",
+  "empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Juttele muille aloittaaksesi keskustelun.",
+  "empty_column.public": "Täällä ei ole mitään! Kirjoita jotain julkisesti, tai käy manuaalisesti seuraamassa käyttäjiä muista instansseista saadaksesi sisältöä",
+  "follow_request.authorize": "Valtuuta",
+  "follow_request.reject": "Hylkää",
+  "getting_started.appsshort": "Sovellukset",
   "getting_started.faq": "FAQ",
   "getting_started.heading": "Aloitus",
-  "getting_started.open_source_notice": "Mastodon Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}.",
-  "getting_started.userguide": "User Guide",
-  "home.column_settings.advanced": "Advanced",
-  "home.column_settings.basic": "Basic",
-  "home.column_settings.filter_regex": "Filter out by regular expressions",
-  "home.column_settings.show_reblogs": "Show boosts",
-  "home.column_settings.show_replies": "Show replies",
-  "home.settings": "Column settings",
-  "keyboard_shortcuts.back": "to navigate back",
-  "keyboard_shortcuts.boost": "to boost",
-  "keyboard_shortcuts.column": "to focus a status in one of the columns",
-  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "getting_started.open_source_notice": "Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}.",
+  "getting_started.userguide": "Käyttöopas",
+  "home.column_settings.advanced": "Tarkemmat asetukset",
+  "home.column_settings.basic": "Perusasetukset",
+  "home.column_settings.filter_regex": "Suodata säännöllisten lauseiden avulla",
+  "home.column_settings.show_reblogs": "Näytä buustaukset",
+  "home.column_settings.show_replies": "Näytä vastaukset",
+  "home.settings": "Sarakeasetukset",
+  "keyboard_shortcuts.back": "liikkuaksesi taaksepäin",
+  "keyboard_shortcuts.boost": "buustataksesi",
+  "keyboard_shortcuts.column": "keskittääksesi statuspäivitykseen yhdessä sarakkeista",
+  "keyboard_shortcuts.compose": "aktivoidaksesi tekstinkirjoitusalueen",
   "keyboard_shortcuts.description": "Description",
-  "keyboard_shortcuts.down": "to move down in the list",
+  "keyboard_shortcuts.down": "liikkuaksesi listassa alaspäin",
   "keyboard_shortcuts.enter": "to open status",
-  "keyboard_shortcuts.favourite": "to favourite",
-  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.hotkey": "Hotkey",
-  "keyboard_shortcuts.legend": "to display this legend",
-  "keyboard_shortcuts.mention": "to mention author",
-  "keyboard_shortcuts.reply": "to reply",
-  "keyboard_shortcuts.search": "to focus search",
-  "keyboard_shortcuts.toot": "to start a brand new toot",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "to move up in the list",
+  "keyboard_shortcuts.favourite": "tykätäksesi",
+  "keyboard_shortcuts.heading": "Näppäinoikotiet",
+  "keyboard_shortcuts.hotkey": "Pikanäppäin",
+  "keyboard_shortcuts.legend": "näyttääksesi tämän selitteen",
+  "keyboard_shortcuts.mention": "mainitaksesi julkaisijan",
+  "keyboard_shortcuts.reply": "vastataksesi",
+  "keyboard_shortcuts.search": "aktivoidaksesi hakukentän",
+  "keyboard_shortcuts.toot": "aloittaaksesi uuden töötin kirjoittamisen",
+  "keyboard_shortcuts.unfocus": "poistaaksesi aktivoinnin tekstikentästä/hakukentästä",
+  "keyboard_shortcuts.up": "liikkuaksesi listassa ylöspäin",
   "lightbox.close": "Sulje",
-  "lightbox.next": "Next",
-  "lightbox.previous": "Previous",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
+  "lightbox.next": "Seuraava",
+  "lightbox.previous": "Edellinen",
+  "lists.account.add": "Lisää listaan",
+  "lists.account.remove": "Poista listalta",
   "lists.delete": "Delete list",
-  "lists.edit": "Edit list",
-  "lists.new.create": "Add list",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Your lists",
+  "lists.edit": "Muokkaa listaa",
+  "lists.new.create": "Lisää lista",
+  "lists.new.title_placeholder": "Uuden listan otsikko",
+  "lists.search": "Etsi seuraamiesi henkilöiden joukosta",
+  "lists.subheading": "Omat listat",
   "loading_indicator.label": "Ladataan...",
-  "media_gallery.toggle_visible": "Toggle visibility",
-  "missing_indicator.label": "Not found",
-  "mute_modal.hide_notifications": "Hide notifications from this user?",
-  "navigation_bar.blocks": "Blocked users",
+  "media_gallery.toggle_visible": "Säädä näkyvyyttä",
+  "missing_indicator.label": "Ei löydetty",
+  "missing_indicator.sublabel": "Tätä resurssia ei löytynyt",
+  "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.edit_profile": "Muokkaa profiilia",
-  "navigation_bar.favourites": "Favourites",
-  "navigation_bar.follow_requests": "Follow requests",
-  "navigation_bar.info": "Extended information",
-  "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
-  "navigation_bar.lists": "Lists",
+  "navigation_bar.favourites": "Suosikit",
+  "navigation_bar.follow_requests": "Seurauspyynnöt",
+  "navigation_bar.info": "Tietoa tästä instanssista",
+  "navigation_bar.keyboard_shortcuts": "Näppäinoikotiet",
+  "navigation_bar.lists": "Listat",
   "navigation_bar.logout": "Kirjaudu ulos",
-  "navigation_bar.mutes": "Muted users",
-  "navigation_bar.pins": "Pinned toots",
+  "navigation_bar.mutes": "Mykistetyt käyttäjät",
+  "navigation_bar.pins": "Kiinnitetyt töötit",
   "navigation_bar.preferences": "Ominaisuudet",
   "navigation_bar.public_timeline": "Yleinen aikajana",
   "notification.favourite": "{name} tykkäsi statuksestasi",
   "notification.follow": "{name} seurasi sinua",
   "notification.mention": "{name} mainitsi sinut",
   "notification.reblog": "{name} buustasi statustasi",
-  "notifications.clear": "Clear notifications",
-  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
+  "notifications.clear": "Tyhjennä ilmoitukset",
+  "notifications.clear_confirmation": "Oletko varma, että haluat lopullisesti tyhjentää kaikki ilmoituksesi?",
   "notifications.column_settings.alert": "Työpöytä ilmoitukset",
   "notifications.column_settings.favourite": "Tykkäyksiä:",
   "notifications.column_settings.follow": "Uusia seuraajia:",
   "notifications.column_settings.mention": "Mainintoja:",
-  "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
+  "notifications.column_settings.push": "Push-ilmoitukset",
+  "notifications.column_settings.push_meta": "Tämä laite",
   "notifications.column_settings.reblog": "Buusteja:",
   "notifications.column_settings.show": "Näytä sarakkeessa",
-  "notifications.column_settings.sound": "Play sound",
-  "onboarding.done": "Done",
-  "onboarding.next": "Next",
-  "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
-  "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
-  "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
-  "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
-  "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
-  "onboarding.page_one.welcome": "Welcome to Mastodon!",
-  "onboarding.page_six.admin": "Your instance's admin is {admin}.",
-  "onboarding.page_six.almost_done": "Almost done...",
-  "onboarding.page_six.appetoot": "Bon Appetoot!",
-  "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
+  "notifications.column_settings.sound": "Soita ääni",
+  "onboarding.done": "Valmis",
+  "onboarding.next": "Seuraava",
+  "onboarding.page_five.public_timelines": "Paikallinen aikajana näyttää kaikki julkiset julkaisut kaikilta, jotka ovat verkko-osoitteessa {domain}. Yleinen aikajana näyttää julkiset julkaisut kaikilta niiltä, joita käyttäjät verkko-osoitteessa {domain} seuraavat. Nämä ovat julkiset aikajanat, ja ne ovat hyviä tapoja löytää uusia ihmisiä.",
+  "onboarding.page_four.home": "Kotiaikajana näyttää julkaisut ihmisiltä joita seuraat.",
+  "onboarding.page_four.notifications": "Ilmoitukset-sarake näyttää sinulle, kun joku on viestii kanssasi.",
+  "onboarding.page_one.federation": "Mastodon on yhteisöpalvelu, joka toimii monen itsenäisen palvelimen muodostamassa verkossa. Me kutsumme näitä palvelimia instansseiksi.",
+  "onboarding.page_one.full_handle": "Koko käyttäjänimesi",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
+  "onboarding.page_one.welcome": "Tervetuloa Mastodoniin!",
+  "onboarding.page_six.admin": "Instanssisi ylläpitäjä on {admin}.",
+  "onboarding.page_six.almost_done": "Melkein valmista...",
+  "onboarding.page_six.appetoot": "Bon Appetööt!",
+  "onboarding.page_six.apps_available": "{apps} on saatavilla iOS:lle, Androidille ja muille alustoille.",
   "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
-  "onboarding.page_six.guidelines": "community guidelines",
-  "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
-  "onboarding.page_six.various_app": "mobile apps",
-  "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
-  "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
-  "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
-  "onboarding.skip": "Skip",
-  "privacy.change": "Adjust status privacy",
-  "privacy.direct.long": "Post to mentioned users only",
-  "privacy.direct.short": "Direct",
-  "privacy.private.long": "Post to followers only",
-  "privacy.private.short": "Followers-only",
-  "privacy.public.long": "Post to public timelines",
-  "privacy.public.short": "Public",
-  "privacy.unlisted.long": "Do not show in public timelines",
-  "privacy.unlisted.short": "Unlisted",
+  "onboarding.page_six.guidelines": "yhteisön säännöt",
+  "onboarding.page_six.read_guidelines": "Ole hyvä ja lue {domain}:n {guidelines}!",
+  "onboarding.page_six.various_app": "mobiilisovellukset",
+  "onboarding.page_three.profile": "Muokkaa profiiliasi muuttaaksesi kuvakettasi, esittelyäsi ja nimimerkkiäsi. Löydät sieltä myös muita henkilökohtaisia asetuksia.",
+  "onboarding.page_three.search": "Käytä hakukenttää löytääksesi ihmisiä ja etsiäksesi hashtageja, kuten {illustration} tai {introductions}. Hakeaksesi henkilöä joka on toisessa instanssissa, käytä hänen käyttäjänimeään kokonaisuudessaan.",
+  "onboarding.page_two.compose": "Kirjoita postauksia kirjoita-sarakkeessa. Voit ladata kuvia, vaihtaa yksityisyysasetuksia ja lisätä sisältövaroituksia alla olevista painikkeista.",
+  "onboarding.skip": "Ohita",
+  "privacy.change": "Säädä töötin yksityisyysasetuksia",
+  "privacy.direct.long": "Julkaise vain mainituille käyttäjille",
+  "privacy.direct.short": "Yksityisviesti",
+  "privacy.private.long": "Julkaise vain seuraajille",
+  "privacy.private.short": "Vain seuraajat",
+  "privacy.public.long": "Julkaise julkisille aikajanoille",
+  "privacy.public.short": "Julkinen",
+  "privacy.unlisted.long": "Älä julkaise yleisillä aikajanoilla",
+  "privacy.unlisted.short": "Julkinen, mutta älä näytä julkisella aikajanalla",
+  "regeneration_indicator.label": "Ladataan…",
+  "regeneration_indicator.sublabel": "Kotinäkymääsi valmistellaan!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
-  "relative_time.just_now": "now",
+  "relative_time.just_now": "nyt",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Peruuta",
-  "report.placeholder": "Additional comments",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+  "report.placeholder": "Lisäkommentit",
   "report.submit": "Submit",
   "report.target": "Reporting",
   "search.placeholder": "Hae",
-  "search_popout.search_format": "Advanced search format",
-  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.search_format": "Tarkennettu haku",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "hashtagi",
   "search_popout.tips.status": "status",
-  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
-  "search_popout.tips.user": "user",
+  "search_popout.tips.text": "Pelkkä tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit",
+  "search_popout.tips.user": "käyttäjä",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
-  "standalone.public_title": "A look inside...",
+  "standalone.public_title": "Kurkistus sisälle...",
   "status.block": "Block @{name}",
-  "status.cannot_reblog": "This post cannot be boosted",
+  "status.cannot_reblog": "Tätä postausta ei voi buustata",
   "status.delete": "Poista",
-  "status.embed": "Embed",
+  "status.embed": "Upota",
   "status.favourite": "Tykkää",
-  "status.load_more": "Load more",
-  "status.media_hidden": "Media hidden",
+  "status.load_more": "Lataa lisää",
+  "status.media_hidden": "Media piilotettu",
   "status.mention": "Mainitse @{name}",
-  "status.more": "More",
-  "status.mute": "Mute @{name}",
-  "status.mute_conversation": "Mute conversation",
-  "status.open": "Expand this status",
-  "status.pin": "Pin on profile",
+  "status.more": "Lisää",
+  "status.mute": "Mykistä @{name}",
+  "status.mute_conversation": "Mykistä keskustelu",
+  "status.open": "Laajenna statuspäivitys",
+  "status.pin": "Kiinnitä profiiliin",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Buustaa",
   "status.reblogged_by": "{name} buustasi",
   "status.reply": "Vastaa",
-  "status.replyAll": "Reply to thread",
+  "status.replyAll": "Vastaa ketjuun",
   "status.report": "Report @{name}",
   "status.sensitive_toggle": "Klikkaa nähdäksesi",
   "status.sensitive_warning": "Arkaluontoista sisältöä",
-  "status.share": "Share",
-  "status.show_less": "Show less",
-  "status.show_more": "Show more",
-  "status.unmute_conversation": "Unmute conversation",
-  "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Luo",
+  "status.share": "Jaa",
+  "status.show_less": "Näytä vähemmän",
+  "status.show_more": "Näytä lisää",
+  "status.unmute_conversation": "Poista mykistys keskustelulta",
+  "status.unpin": "Irrota profiilista",
   "tabs_bar.federated_timeline": "Federated",
   "tabs_bar.home": "Koti",
-  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.local_timeline": "Paikallinen",
   "tabs_bar.notifications": "Ilmoitukset",
-  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
-  "upload_area.title": "Drag & drop to upload",
+  "ui.beforeunload": "Luonnoksesi menetetään, jos poistut Mastodonista.",
+  "upload_area.title": "Raahaa ja pudota tähän ladataksesi",
   "upload_button.label": "Lisää mediaa",
-  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.description": "Anna kuvaus näkörajoitteisia varten",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Peru",
-  "upload_progress.label": "Uploading...",
-  "video.close": "Close video",
-  "video.exit_fullscreen": "Exit full screen",
-  "video.expand": "Expand video",
+  "upload_progress.label": "Ladataan...",
+  "video.close": "Sulje video",
+  "video.exit_fullscreen": "Poistu koko näytön tilasta",
+  "video.expand": "Laajenna video",
   "video.fullscreen": "Full screen",
-  "video.hide": "Hide video",
-  "video.mute": "Mute sound",
-  "video.pause": "Pause",
-  "video.play": "Play",
-  "video.unmute": "Unmute sound"
+  "video.hide": "Piilota video",
+  "video.mute": "Mykistä ääni",
+  "video.pause": "Keskeytä",
+  "video.play": "Toista",
+  "video.unmute": "Poista mykistys ääneltä"
 }
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index e77107fc5..4df1ef30f 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -1,7 +1,9 @@
 {
-  "account.block": "Bloquer",
-  "account.block_domain": "Tout masquer de {domain}",
+  "account.block": "Bloquer @{name}",
+  "account.block_domain": "Tout masquer venant de {domain}",
+  "account.blocked": "Bloqué",
   "account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.",
+  "account.domain_blocked": "Domaine caché",
   "account.edit_profile": "Modifier le profil",
   "account.follow": "Suivre",
   "account.followers": "Abonné⋅e⋅s",
@@ -11,9 +13,11 @@
   "account.media": "Média",
   "account.mention": "Mentionner",
   "account.moved_to": "{name} a déménagé vers :",
-  "account.mute": "Masquer",
+  "account.mute": "Masquer @{name}",
   "account.mute_notifications": "Ignorer les notifications de @{name}",
-  "account.posts": "Statuts",
+  "account.muted": "Silencé",
+  "account.posts": "Pouets",
+  "account.posts_with_replies": "Pouets avec réponses",
   "account.report": "Signaler",
   "account.requested": "Invitation envoyée",
   "account.share": "Partager le profil de @{name}",
@@ -36,7 +40,7 @@
   "column.favourites": "Favoris",
   "column.follow_requests": "Demandes de suivi",
   "column.home": "Accueil",
-  "column.lists": "Lists",
+  "column.lists": "Listes",
   "column.mutes": "Comptes masqués",
   "column.notifications": "Notifications",
   "column.pins": "Pouets épinglés",
@@ -50,7 +54,7 @@
   "column_header.unpin": "Retirer",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Paramètres",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "Ce pouet ne sera pas listé dans les recherches par hashtag car sa visibilité est réglée sur \"non-listé\". Seuls les pouets avec une visibilité \"publique\" peuvent être recherchés par hashtag.",
   "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.",
   "compose_form.lock_disclaimer.lock": "verrouillé",
   "compose_form.placeholder": "Qu’avez-vous en tête ?",
@@ -73,7 +77,7 @@
   "confirmations.unfollow.confirm": "Ne plus suivre",
   "confirmations.unfollow.message": "Voulez-vous arrêter de suivre {name} ?",
   "embed.instructions": "Intégrez ce statut à votre site en copiant le code ci-dessous.",
-  "embed.preview": "Il apparaîtra comme cela :",
+  "embed.preview": "Il apparaîtra comme cela :",
   "emoji_button.activity": "Activités",
   "emoji_button.custom": "Personnalisés",
   "emoji_button.flags": "Drapeaux",
@@ -89,12 +93,12 @@
   "emoji_button.symbols": "Symboles",
   "emoji_button.travel": "Lieux et voyages",
   "empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !",
-  "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag",
+  "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.",
   "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateur⋅ice⋅s.",
   "empty_column.home.public_timeline": "le fil public",
   "empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette liste publierons de nouveaux statuts, ils apparaîtront ici.",
   "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateur⋅ice⋅s pour débuter la conversation.",
-  "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateur⋅ice⋅s d’autres instances pour remplir le fil public.",
+  "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateur⋅ice·s d’autres instances pour remplir le fil public",
   "follow_request.authorize": "Accepter",
   "follow_request.reject": "Rejeter",
   "getting_started.appsshort": "Applications",
@@ -121,10 +125,10 @@
   "keyboard_shortcuts.legend": "pour afficher cette légende",
   "keyboard_shortcuts.mention": "pour mentionner l'auteur",
   "keyboard_shortcuts.reply": "pour répondre",
-  "keyboard_shortcuts.search": "to focus search",
+  "keyboard_shortcuts.search": "pour cibler la recherche",
   "keyboard_shortcuts.toot": "pour démarrer un tout nouveau pouet",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "to move up in the list",
+  "keyboard_shortcuts.unfocus": "pour recentrer composer textarea/search",
+  "keyboard_shortcuts.up": "pour remonter dans la liste",
   "lightbox.close": "Fermer",
   "lightbox.next": "Suivant",
   "lightbox.previous": "Précédent",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Chargement…",
   "media_gallery.toggle_visible": "Modifier la visibilité",
   "missing_indicator.label": "Non trouvé",
+  "missing_indicator.sublabel": "Ressource introuvable",
   "mute_modal.hide_notifications": "Masquer les notifications de cet utilisateur ?",
   "navigation_bar.blocks": "Comptes bloqués",
   "navigation_bar.community_timeline": "Fil public local",
@@ -154,15 +159,15 @@
   "navigation_bar.preferences": "Préférences",
   "navigation_bar.public_timeline": "Fil public global",
   "notification.favourite": "{name} a ajouté à ses favoris :",
-  "notification.follow": "{name} vous suit.",
+  "notification.follow": "{name} vous suit",
   "notification.mention": "{name} vous a mentionné⋅e :",
   "notification.reblog": "{name} a partagé votre statut :",
   "notifications.clear": "Nettoyer",
   "notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?",
   "notifications.column_settings.alert": "Notifications locales",
   "notifications.column_settings.favourite": "Favoris :",
-  "notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e⋅s :",
-  "notifications.column_settings.mention": "Mentions :",
+  "notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e·s :",
+  "notifications.column_settings.mention": "Mentions :",
   "notifications.column_settings.push": "Notifications push",
   "notifications.column_settings.push_meta": "Cet appareil",
   "notifications.column_settings.reblog": "Partages :",
@@ -171,15 +176,16 @@
   "onboarding.done": "Effectué",
   "onboarding.next": "Suivant",
   "onboarding.page_five.public_timelines": "Le fil public global affiche les posts de tou⋅te⋅s les utilisateur⋅ice⋅s suivi⋅es par les membres de {domain}. Le fil public local est identique mais se limite aux utilisateur⋅ice⋅s de {domain}.",
-  "onboarding.page_four.home": "L’Accueil affiche les posts de tou⋅te⋅s les utilisateur⋅ice⋅s que vous suivez",
-  "onboarding.page_four.notifications": "Les Notifications vous informent lorsque quelqu’un interagit avec vous",
+  "onboarding.page_four.home": "L’Accueil affiche les posts de tou⋅te·s les utilisateur⋅ice·s que vous suivez.",
+  "onboarding.page_four.notifications": "Les Notifications vous informent lorsque quelqu’un interagit avec vous.",
   "onboarding.page_one.federation": "Mastodon est un réseau social qui appartient à tou⋅te⋅s.",
-  "onboarding.page_one.handle": "Vous êtes sur {domain}, une des nombreuses instances indépendantes de Mastodon. Votre nom d’utilisateur⋅ice complet est {handle}",
+  "onboarding.page_one.full_handle": "Votre pleine maîtrise",
+  "onboarding.page_one.handle_hint": "C'est ce que vous diriez à vos amis de rechercher.",
   "onboarding.page_one.welcome": "Bienvenue sur Mastodon !",
-  "onboarding.page_six.admin": "L’administrateur⋅ice de votre instance est {admin}",
+  "onboarding.page_six.admin": "L’administrateur⋅ice de votre instance est {admin}.",
   "onboarding.page_six.almost_done": "Nous y sommes presque…",
   "onboarding.page_six.appetoot": "Bon appouétit !",
-  "onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant… Bon appouétit !",
+  "onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres.",
   "onboarding.page_six.github": "Mastodon est un logiciel libre, gratuit et open-source. Vous pouvez rapporter des bogues, suggérer des fonctionnalités, ou contribuer à son développement sur {github}.",
   "onboarding.page_six.guidelines": "règles de la communauté",
   "onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !",
@@ -189,29 +195,38 @@
   "onboarding.page_two.compose": "Écrivez depuis la colonne de composition. Vous pouvez ajouter des images, changer les réglages de confidentialité, et ajouter des avertissements de contenu (Content Warning) grâce aux icônes en dessous.",
   "onboarding.skip": "Passer",
   "privacy.change": "Ajuster la confidentialité du message",
-  "privacy.direct.long": "N’afficher que pour les personnes mentionnées",
+  "privacy.direct.long": "N'envoyer qu'aux personnes mentionnées",
   "privacy.direct.short": "Direct",
-  "privacy.private.long": "N’afficher que pour vos abonné⋅e⋅s",
+  "privacy.private.long": "N'envoyer qu'à vos abonné⋅e⋅s",
   "privacy.private.short": "Privé",
   "privacy.public.long": "Afficher dans les fils publics",
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Ne pas afficher dans les fils publics",
   "privacy.unlisted.short": "Non-listé",
-  "relative_time.days": "{number} j",
-  "relative_time.hours": "{number} h",
+  "regeneration_indicator.label": "Chargement…",
+  "regeneration_indicator.sublabel": "Le flux de votre page principale est en cours de préparation !",
+  "relative_time.days": "{number} j",
+  "relative_time.hours": "{number} h",
   "relative_time.just_now": "à l’instant",
   "relative_time.minutes": "{number} min",
   "relative_time.seconds": "{number} s",
   "reply_indicator.cancel": "Annuler",
+  "report.forward": "Transférer à {target}",
+  "report.forward_hint": "Le compte provient d'un autre serveur. Envoyez également une copie anonyme du rapport ?",
+  "report.hint": "Le rapport sera envoyé aux modérateurs de votre instance. Vous pouvez expliquer pourquoi vous signalez ce compte ci-dessous :",
   "report.placeholder": "Commentaires additionnels",
   "report.submit": "Envoyer",
   "report.target": "Signalement",
   "search.placeholder": "Rechercher",
   "search_popout.search_format": "Recherche avancée",
+  "search_popout.tips.full_text": "Les textes simples retournent les pouets que vous avez écris, mis en favori, épinglés, ou ayant été mentionnés, ainsi que les noms d'utilisateurs, les noms affichés, et les hashtags correspondant.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "statuts",
   "search_popout.tips.text": "Un texte simple renvoie les noms affichés, les noms d’utilisateur⋅ice et les hashtags correspondants",
   "search_popout.tips.user": "utilisateur⋅ice",
+  "search_results.accounts": "Personnes",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Pouets",
   "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
   "standalone.public_title": "Jeter un coup d’œil…",
   "status.block": "Block @{name}",
@@ -223,10 +238,11 @@
   "status.media_hidden": "Média caché",
   "status.mention": "Mentionner",
   "status.more": "Plus",
-  "status.mute": "Mute @{name}",
+  "status.mute": "Masquer @{name}",
   "status.mute_conversation": "Masquer la conversation",
   "status.open": "Déplier ce statut",
   "status.pin": "Épingler sur le profil",
+  "status.pinned": "Pouet épinglé",
   "status.reblog": "Partager",
   "status.reblogged_by": "{name} a partagé :",
   "status.reply": "Répondre",
@@ -239,7 +255,6 @@
   "status.show_more": "Déplier",
   "status.unmute_conversation": "Ne plus masquer la conversation",
   "status.unpin": "Retirer du profil",
-  "tabs_bar.compose": "Composer",
   "tabs_bar.federated_timeline": "Fil public global",
   "tabs_bar.home": "Accueil",
   "tabs_bar.local_timeline": "Fil public local",
@@ -248,6 +263,7 @@
   "upload_area.title": "Glissez et déposez pour envoyer",
   "upload_button.label": "Joindre un média",
   "upload_form.description": "Décrire pour les malvoyants",
+  "upload_form.focus": "Recadrer",
   "upload_form.undo": "Annuler",
   "upload_progress.label": "Envoi en cours…",
   "video.close": "Fermer la vidéo",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 77f6b82ab..e222ddaea 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Bloquear @{name}",
   "account.block_domain": "Ocultar calquer contido de {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Editar perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidoras",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} marchou a:",
   "account.mute": "Acalar @{name}",
   "account.mute_notifications": "Acalar as notificacións de @{name}",
-  "account.posts": "Publicacións",
+  "account.muted": "Muted",
+  "account.posts": "Toots",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Informar sobre @{name}",
   "account.requested": "Agardando aceptación. Pulse para cancelar a solicitude de seguimento",
   "account.share": "Compartir o perfil de @{name}",
@@ -50,7 +54,7 @@
   "column_header.unpin": "Soltar",
   "column_subheading.navigation": "Navegación",
   "column_subheading.settings": "Axustes",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "Esta mensaxe non será listada baixo ningunha etiqueta xa que está marcada como non listada. Só os toots públicos poden buscarse por etiquetas.",
   "compose_form.lock_disclaimer": "A súa conta non está {locked}. Calquera pode seguila para ver as súas mensaxes só-para-seguidoras.",
   "compose_form.lock_disclaimer.lock": "bloqueado",
   "compose_form.placeholder": "A qué andas?",
@@ -139,8 +143,9 @@
   "loading_indicator.label": "Cargando...",
   "media_gallery.toggle_visible": "Dar visibilidade",
   "missing_indicator.label": "Non atopado",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Esconder notificacións deste usuario?",
-  "navigation_bar.blocks": "Usuarios bloqueados",
+  "navigation_bar.blocks": "Usuarias bloqueadas",
   "navigation_bar.community_timeline": "Liña temporal local",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritas",
@@ -156,7 +161,7 @@
   "notification.favourite": "{name} marcou como favorito o seu estado",
   "notification.follow": "{name} está a seguila",
   "notification.mention": "{name} mencionoute",
-  "notification.reblog": "{name} promocionou o seu estado",
+  "notification.reblog": "{name} promoveu o seu estado",
   "notifications.clear": "Limpar notificacións",
   "notifications.clear_confirmation": "Estás seguro de que queres limpar permanentemente todas as túas notificacións?",
   "notifications.column_settings.alert": "Notificacións de escritorio",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "A liña de tempo local mostra as publicacións das persoas que segues.",
   "onboarding.page_four.notifications": "A columna de notificacións mostra cando alguén interactúa contigo.",
   "onboarding.page_one.federation": "Mastodon é unha rede de servidores independentes que se unen para facer unha rede social máis grande. Chamamos instancias a estes servidores.",
-  "onboarding.page_one.handle": "Estás en {domain}, polo que o teu nome de usuario completo é {handle}",
+  "onboarding.page_one.full_handle": "O seu alcume completo",
+  "onboarding.page_one.handle_hint": "Esto é o que lle debe dicir a quen queira seguila.",
   "onboarding.page_one.welcome": "Benvido a Mastodon!",
   "onboarding.page_six.admin": "O administrador da túa instancia é {admin}.",
   "onboarding.page_six.almost_done": "Case feito...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Pública",
   "privacy.unlisted.long": "Non publicar en liñas temporais públicas",
   "privacy.unlisted.short": "Non listada",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "agora",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancelar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Comentarios adicionais",
   "report.submit": "Enviar",
   "report.target": "Informar {target}",
   "search.placeholder": "Buscar",
   "search_popout.search_format": "Formato de busca avanzada",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "etiqueta",
   "search_popout.tips.status": "estado",
   "search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas",
   "search_popout.tips.user": "usuaria",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count,plural,one {result} outros {results}}",
   "standalone.public_title": "Ollada dentro...",
   "status.block": "Block @{name}",
@@ -223,10 +238,11 @@
   "status.media_hidden": "Medios ocultos",
   "status.mention": "Mencionar @{name}",
   "status.more": "Máis",
-  "status.mute": "Mute @{name}",
+  "status.mute": "Acalar @{name}",
   "status.mute_conversation": "Acalar conversa",
   "status.open": "Expandir este estado",
   "status.pin": "Fixar no perfil",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Promover",
   "status.reblogged_by": "{name} promoveu",
   "status.reply": "Resposta",
@@ -239,7 +255,6 @@
   "status.show_more": "Mostrar máis",
   "status.unmute_conversation": "Non acalar a conversa",
   "status.unpin": "Despegar do perfil",
-  "tabs_bar.compose": "Compoñer",
   "tabs_bar.federated_timeline": "Federado",
   "tabs_bar.home": "Inicio",
   "tabs_bar.local_timeline": "Local",
@@ -248,6 +263,7 @@
   "upload_area.title": "Arrastre e solte para subir",
   "upload_button.label": "Engadir medios",
   "upload_form.description": "Describa para deficientes visuais",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Desfacer",
   "upload_progress.label": "Subindo...",
   "video.close": "Pechar video",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index 2eb186173..b31976c42 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -1,7 +1,9 @@
 {
   "account.block": "חסימת @{name}",
   "account.block_domain": "להסתיר הכל מהקהילה {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "המידע להלן עשוי להיות לא עדכני או לא שלם.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "עריכת פרופיל",
   "account.follow": "מעקב",
   "account.followers": "עוקבים",
@@ -13,7 +15,9 @@
   "account.moved_to": "החשבון {name} הועבר אל:",
   "account.mute": "להשתיק את @{name}",
   "account.mute_notifications": "להסתיר התראות מאת @{name}",
+  "account.muted": "Muted",
   "account.posts": "הודעות",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "לדווח על @{name}",
   "account.requested": "בהמתנה לאישור",
   "account.share": "לשתף את אודות @{name}",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "טוען...",
   "media_gallery.toggle_visible": "נראה\\בלתי נראה",
   "missing_indicator.label": "לא נמצא",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "להסתיר הודעות מחשבון זה?",
   "navigation_bar.blocks": "חסימות",
   "navigation_bar.community_timeline": "ציר זמן מקומי",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "ציר זמן הבית מראה הודעות מהנעקבים שלך.",
   "onboarding.page_four.notifications": "טור ההתראות מראה כשמישהו מתייחס להודעות שלך.",
   "onboarding.page_one.federation": "מסטודון היא רשת של שרתים עצמאיים מצורפים ביחד לכדי רשת חברתית אחת גדולה. אנחנו מכנים את השרתים האלו קהילות.",
-  "onboarding.page_one.handle": "אתם בקהילה {domain}, ולכן מזהה המשתמש המלא שלכם הוא {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "ברוכים הבאים למסטודון!",
   "onboarding.page_six.admin": "הקהילה מנוהלת בידי {admin}.",
   "onboarding.page_six.almost_done": "כמעט סיימנו...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "פומבי",
   "privacy.unlisted.long": "לא יופיע בפידים הציבוריים המשותפים",
   "privacy.unlisted.short": "לא לפיד הכללי",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "כרגע",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "ביטול",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "הערות נוספות",
   "report.submit": "שליחה",
   "report.target": "דיווח",
   "search.placeholder": "חיפוש",
   "search_popout.search_format": "מבנה חיפוש מתקדם",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "האשתג",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "טקסט פשוט מחזיר כינויים, שמות משתמש והאשתגים",
   "search_popout.tips.user": "משתמש(ת)",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
   "standalone.public_title": "הצצה פנימה...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "השתקת שיחה",
   "status.open": "הרחבת הודעה",
   "status.pin": "לקבע באודות",
+  "status.pinned": "Pinned toot",
   "status.reblog": "הדהוד",
   "status.reblogged_by": "הודהד על ידי {name}",
   "status.reply": "תגובה",
@@ -239,7 +255,6 @@
   "status.show_more": "הראה יותר",
   "status.unmute_conversation": "הסרת השתקת שיחה",
   "status.unpin": "לשחרר מקיבוע באודות",
-  "tabs_bar.compose": "חיבור",
   "tabs_bar.federated_timeline": "ציר זמן בין-קהילתי",
   "tabs_bar.home": "בבית",
   "tabs_bar.local_timeline": "ציר זמן מקומי",
@@ -248,6 +263,7 @@
   "upload_area.title": "ניתן להעלות על ידי Drag & drop",
   "upload_button.label": "הוספת מדיה",
   "upload_form.description": "תיאור לכבדי ראיה",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "ביטול",
   "upload_progress.label": "עולה...",
   "video.close": "סגירת וידאו",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index 00dea67f7..d176a5df6 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Blokiraj @{name}",
   "account.block_domain": "Sakrij sve sa {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Uredi profil",
   "account.follow": "Slijedi",
   "account.followers": "Sljedbenici",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "Utišaj @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
   "account.posts": "Postovi",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Prijavi @{name}",
   "account.requested": "Čeka pristanak",
   "account.share": "Share @{name}'s profile",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Učitavam...",
   "media_gallery.toggle_visible": "Preklopi vidljivost",
   "missing_indicator.label": "Nije nađen",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blokirani korisnici",
   "navigation_bar.community_timeline": "Lokalni timeline",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "The home timeline prikazuje postove ljudi koje slijediš.",
   "onboarding.page_four.notifications": "Stupac za notifikacije pokazuje poruke drugih upućene tebi.",
   "onboarding.page_one.federation": "Mastodon čini mreža  neovisnih servera udruženih u jednu veću socialnu mrežu. Te servere nazivamo instancama.",
-  "onboarding.page_one.handle": "Ti si na {domain}, i tvoja puna handle je {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Dobro došli na Mastodon!",
   "onboarding.page_six.admin": "Administrator tvoje instance je {admin}.",
   "onboarding.page_six.almost_done": "Još malo pa gotovo...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Javno",
   "privacy.unlisted.long": "Ne prikazuj u javnim timelineovima",
   "privacy.unlisted.short": "Unlisted",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Otkaži",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Dodatni komentari",
   "report.submit": "Pošalji",
   "report.target": "Prijavljivanje",
   "search.placeholder": "Traži",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Utišaj razgovor",
   "status.open": "Proširi ovaj status",
   "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Podigni",
   "status.reblogged_by": "{name} je podigao",
   "status.reply": "Odgovori",
@@ -239,7 +255,6 @@
   "status.show_more": "Pokaži više",
   "status.unmute_conversation": "Poništi utišavanje razgovora",
   "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Sastavi",
   "tabs_bar.federated_timeline": "Federalni",
   "tabs_bar.home": "Dom",
   "tabs_bar.local_timeline": "Lokalno",
@@ -248,6 +263,7 @@
   "upload_area.title": "Povuci i spusti kako bi uploadao",
   "upload_button.label": "Dodaj media",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Poništi",
   "upload_progress.label": "Uploadam...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index e1048519b..a4d2091ef 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -1,253 +1,269 @@
 {
-  "account.block": "Blokkolás",
-  "account.block_domain": "Hide everything from {domain}",
-  "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.block": "@{name} letiltása",
+  "account.block_domain": "Minden elrejtése innen: {domain}",
+  "account.blocked": "Blocked",
+  "account.disclaimer_full": "Az alul található információk hiányosan mutathatják be a felhasználót.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Profil szerkesztése",
   "account.follow": "Követés",
   "account.followers": "Követők",
   "account.follows": "Követve",
   "account.follows_you": "Követnek téged",
-  "account.hide_reblogs": "Hide boosts from @{name}",
-  "account.media": "Media",
-  "account.mention": "Említés",
-  "account.moved_to": "{name} has moved to:",
-  "account.mute": "Mute @{name}",
-  "account.mute_notifications": "Mute notifications from @{name}",
-  "account.posts": "Posts",
-  "account.report": "Report @{name}",
-  "account.requested": "Awaiting approval",
-  "account.share": "Share @{name}'s profile",
-  "account.show_reblogs": "Show boosts from @{name}",
-  "account.unblock": "Blokkolás levétele",
-  "account.unblock_domain": "Unhide {domain}",
+  "account.hide_reblogs": "Rejtsd el a tülkölést @{name}-tól/től",
+  "account.media": "Média",
+  "account.mention": "@{name} említése",
+  "account.moved_to": "{name} átköltözött:",
+  "account.mute": "@{name} némítása",
+  "account.mute_notifications": "@{name} értesítések némítása",
+  "account.muted": "Muted",
+  "account.posts": "Státuszok",
+  "account.posts_with_replies": "Toots with replies",
+  "account.report": "@{name} jelentése",
+  "account.requested": "Engedélyre vár. Kattintson a követési kérés visszavonására",
+  "account.share": "@{name} profiljának megosztása",
+  "account.show_reblogs": "@{name} kedvenceinek mutatása",
+  "account.unblock": "@{name} kiblokkolása",
+  "account.unblock_domain": "{domain} mutatása",
   "account.unfollow": "Követés abbahagyása",
-  "account.unmute": "Unmute @{name}",
-  "account.unmute_notifications": "Unmute notifications from @{name}",
-  "account.view_full_profile": "View full profile",
-  "boost_modal.combo": "You can press {combo} to skip this next time",
-  "bundle_column_error.body": "Something went wrong while loading this component.",
-  "bundle_column_error.retry": "Try again",
-  "bundle_column_error.title": "Network error",
-  "bundle_modal_error.close": "Close",
-  "bundle_modal_error.message": "Something went wrong while loading this component.",
-  "bundle_modal_error.retry": "Try again",
-  "column.blocks": "Blocked users",
-  "column.community": "Local timeline",
-  "column.favourites": "Favourites",
-  "column.follow_requests": "Follow requests",
+  "account.unmute": "@{name} kinémítása",
+  "account.unmute_notifications": "@{name} értesítéseinek kinémítása",
+  "account.view_full_profile": "Teljes profil megtekintése",
+  "boost_modal.combo": "Megnyomhatod {combo}, hogy átugord következő alkalommal",
+  "bundle_column_error.body": "Hiba történt a komponens betöltése közben.",
+  "bundle_column_error.retry": "Próbálja újra",
+  "bundle_column_error.title": "Hálózati hiba",
+  "bundle_modal_error.close": "Bezár",
+  "bundle_modal_error.message": "Hiba történt a komponens betöltésekor.",
+  "bundle_modal_error.retry": "Próbálja újra",
+  "column.blocks": "Letiltott felhasználók",
+  "column.community": "Helyi idővonal",
+  "column.favourites": "Kedvencek",
+  "column.follow_requests": "Követési kérések",
   "column.home": "Kezdőlap",
-  "column.lists": "Lists",
-  "column.mutes": "Muted users",
+  "column.lists": "Listák",
+  "column.mutes": "Némított felhasználók",
   "column.notifications": "Értesítések",
-  "column.pins": "Pinned toot",
-  "column.public": "Nyilvános",
+  "column.pins": "Kitűzött tülkölések",
+  "column.public": "Nyilvános idővonal",
   "column_back_button.label": "Vissza",
-  "column_header.hide_settings": "Hide settings",
-  "column_header.moveLeft_settings": "Move column to the left",
-  "column_header.moveRight_settings": "Move column to the right",
-  "column_header.pin": "Pin",
-  "column_header.show_settings": "Show settings",
-  "column_header.unpin": "Unpin",
-  "column_subheading.navigation": "Navigation",
-  "column_subheading.settings": "Settings",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
-  "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
-  "compose_form.lock_disclaimer.lock": "locked",
+  "column_header.hide_settings": "Beállítások elrejtése",
+  "column_header.moveLeft_settings": "Oszlop elmozdítása balra",
+  "column_header.moveRight_settings": "oszlop elmozdítása jobbra",
+  "column_header.pin": "Kitűz",
+  "column_header.show_settings": "Beállítások mutatása",
+  "column_header.unpin": "Kitűzés eltávolítása",
+  "column_subheading.navigation": "Navigáció",
+  "column_subheading.settings": "Beállítások",
+  "compose_form.hashtag_warning": "Ezen tülkölés nem fog megjelenni semmilyen hashtag alatt mivel listázatlan. Csak a publikus tülkölések kereshetőek hashtag-el.",
+  "compose_form.lock_disclaimer": "Az ön fiókja nincs {locked}. Bárki követni tud, hogy megtekintse a kizárt követőknek szánt üzeneteid.",
+  "compose_form.lock_disclaimer.lock": "lezárva",
   "compose_form.placeholder": "Mire gondolsz?",
-  "compose_form.publish": "Tülk!",
+  "compose_form.publish": "Tülk",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive": "Tartalom érzékenynek jelölése",
-  "compose_form.spoiler": "Hide text behind warning",
-  "compose_form.spoiler_placeholder": "Content warning",
-  "confirmation_modal.cancel": "Cancel",
-  "confirmations.block.confirm": "Block",
-  "confirmations.block.message": "Are you sure you want to block {name}?",
-  "confirmations.delete.confirm": "Delete",
-  "confirmations.delete.message": "Are you sure you want to delete this status?",
-  "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
-  "confirmations.domain_block.confirm": "Hide entire domain",
-  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
-  "confirmations.mute.confirm": "Mute",
-  "confirmations.mute.message": "Are you sure you want to mute {name}?",
-  "confirmations.unfollow.confirm": "Unfollow",
-  "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
-  "embed.instructions": "Embed this status on your website by copying the code below.",
-  "embed.preview": "Here is what it will look like:",
-  "emoji_button.activity": "Activity",
-  "emoji_button.custom": "Custom",
-  "emoji_button.flags": "Flags",
-  "emoji_button.food": "Food & Drink",
-  "emoji_button.label": "Insert emoji",
-  "emoji_button.nature": "Nature",
-  "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
-  "emoji_button.objects": "Objects",
-  "emoji_button.people": "People",
-  "emoji_button.recent": "Frequently used",
-  "emoji_button.search": "Search...",
-  "emoji_button.search_results": "Search results",
-  "emoji_button.symbols": "Symbols",
-  "emoji_button.travel": "Travel & Places",
-  "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
-  "empty_column.hashtag": "There is nothing in this hashtag yet.",
-  "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
-  "empty_column.home.public_timeline": "the public timeline",
-  "empty_column.list": "There is nothing in this list yet.",
-  "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
-  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
-  "follow_request.authorize": "Authorize",
-  "follow_request.reject": "Reject",
-  "getting_started.appsshort": "Apps",
-  "getting_started.faq": "FAQ",
+  "compose_form.spoiler": "Szöveg figyelmeztetés mögé rejtése",
+  "compose_form.spoiler_placeholder": "Figyelmeztetését írja ide",
+  "confirmation_modal.cancel": "Bezár",
+  "confirmations.block.confirm": "Letilt",
+  "confirmations.block.message": "Biztos benne, hogy le szeretné tiltani {name}?",
+  "confirmations.delete.confirm": "Töröl",
+  "confirmations.delete.message": "Biztos benne, hogy törölni szeretné ezt a státuszt?",
+  "confirmations.delete_list.confirm": "Töröl",
+  "confirmations.delete_list.message": "Biztos benne, hogy véglegesen törölni szeretné ezt a listát?",
+  "confirmations.domain_block.confirm": "Egész domain elrejtése",
+  "confirmations.domain_block.message": "Nagyon biztos abban, hogy le szeretné tiltani az egész {domain}-t? A legtöbb esetben néhány célszerű tiltás vagy némítás elegendő és kívánatosabb megoldás.",
+  "confirmations.mute.confirm": "Némít",
+  "confirmations.mute.message": "Biztos benne, hogy némítani szeretné {name}?",
+  "confirmations.unfollow.confirm": "Követés visszavonása",
+  "confirmations.unfollow.message": "Biztos benne, hogy vissza szeretné vonni {name} követését?",
+  "embed.instructions": "Ágyazza be ezen státuszt weboldalába az alábbi kód másolásával.",
+  "embed.preview": "Így fog kinézni:",
+  "emoji_button.activity": "Aktivitás",
+  "emoji_button.custom": "Egyéni",
+  "emoji_button.flags": "Zászlók",
+  "emoji_button.food": "Étel és Ital",
+  "emoji_button.label": "Emoji beszúrása",
+  "emoji_button.nature": "Természet",
+  "emoji_button.not_found": "Nincsenek emojok!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Tárgyak",
+  "emoji_button.people": "Emberek",
+  "emoji_button.recent": "Gyakran használt",
+  "emoji_button.search": "Keresés...",
+  "emoji_button.search_results": "Keresési találatok",
+  "emoji_button.symbols": "Szimbólumok",
+  "emoji_button.travel": "Utazás és Helyek",
+  "empty_column.community": "A helyi idővonal üres. Írj egy publikus stástuszt, hogy elindítsd a labdát!",
+  "empty_column.hashtag": "Jelenleg nem található semmi ezen hashtaggel.",
+  "empty_column.home": "A hazai idővonala üres! Látogasd meg a {public} vagy használd a keresőt, hogy ismerj meg más felhasználókat.",
+  "empty_column.home.public_timeline": "publikus idővonal",
+  "empty_column.list": "A lista jelenleg üres. Mikor a listatagok új státuszt posztolnak itt meg fognak jelenni.",
+  "empty_column.notifications": "Jelenleg nincsenek értesítései. Lépj kapcsolatba másokkal, hogy indítsd el a beszélgetést.",
+  "empty_column.public": "Jelenleg semmi nincs itt! Írj valamit publikusan vagy kövess más szervereken levő felhasználókat, hogy megtöltsd",
+  "follow_request.authorize": "Engedélyez",
+  "follow_request.reject": "Visszautasít",
+  "getting_started.appsshort": "Applikációk",
+  "getting_started.faq": "GYIK",
   "getting_started.heading": "Első lépések",
-  "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
-  "getting_started.userguide": "User Guide",
-  "home.column_settings.advanced": "Advanced",
-  "home.column_settings.basic": "Basic",
-  "home.column_settings.filter_regex": "Filter out by regular expressions",
-  "home.column_settings.show_reblogs": "Show boosts",
-  "home.column_settings.show_replies": "Show replies",
-  "home.settings": "Column settings",
-  "keyboard_shortcuts.back": "to navigate back",
-  "keyboard_shortcuts.boost": "to boost",
-  "keyboard_shortcuts.column": "to focus a status in one of the columns",
-  "keyboard_shortcuts.compose": "to focus the compose textarea",
-  "keyboard_shortcuts.description": "Description",
-  "keyboard_shortcuts.down": "to move down in the list",
-  "keyboard_shortcuts.enter": "to open status",
-  "keyboard_shortcuts.favourite": "to favourite",
-  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.hotkey": "Hotkey",
-  "keyboard_shortcuts.legend": "to display this legend",
-  "keyboard_shortcuts.mention": "to mention author",
-  "keyboard_shortcuts.reply": "to reply",
-  "keyboard_shortcuts.search": "to focus search",
-  "keyboard_shortcuts.toot": "to start a brand new toot",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "to move up in the list",
+  "getting_started.open_source_notice": "Mastodon egy nyílt forráskódú szoftver. Hozzájárulás vagy problémák jelentése a GitHub-on {github}.",
+  "getting_started.userguide": "Használati Útmutató",
+  "home.column_settings.advanced": "Fejlett",
+  "home.column_settings.basic": "Alap",
+  "home.column_settings.filter_regex": "Szűrje ki reguláris kifejezésekkel",
+  "home.column_settings.show_reblogs": "Ismétlések mutatása",
+  "home.column_settings.show_replies": "Válaszok mutatása",
+  "home.settings": "Oszlop beállításai",
+  "keyboard_shortcuts.back": "vissza navigálás",
+  "keyboard_shortcuts.boost": "ismétlés",
+  "keyboard_shortcuts.column": "összpontosítson egy státuszra az egyik oszlopban",
+  "keyboard_shortcuts.compose": "fókuszálja a szerkesztési szövegdobozt",
+  "keyboard_shortcuts.description": "Leírás",
+  "keyboard_shortcuts.down": "lefele navigálás a listában",
+  "keyboard_shortcuts.enter": "státusz megnyitása",
+  "keyboard_shortcuts.favourite": "kedvenccé tétel",
+  "keyboard_shortcuts.heading": "Billentyű rövidítések",
+  "keyboard_shortcuts.hotkey": "Gyorsbillentyű",
+  "keyboard_shortcuts.legend": "jelmagyarázat megjelenítése",
+  "keyboard_shortcuts.mention": "szerző megjelenítése",
+  "keyboard_shortcuts.reply": "válaszolás",
+  "keyboard_shortcuts.search": "kereső kiemelése",
+  "keyboard_shortcuts.toot": "új tülk megkezdése",
+  "keyboard_shortcuts.unfocus": "tülk szerkesztés/keresés fókuszpontból való kivétele",
+  "keyboard_shortcuts.up": "fennebb helyezés a listában",
   "lightbox.close": "Bezárás",
-  "lightbox.next": "Next",
-  "lightbox.previous": "Previous",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
-  "lists.delete": "Delete list",
-  "lists.edit": "Edit list",
-  "lists.new.create": "Add list",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Your lists",
+  "lightbox.next": "Következő",
+  "lightbox.previous": "Előző",
+  "lists.account.add": "Hozzáadás a listához",
+  "lists.account.remove": "Eltávolít a listából",
+  "lists.delete": "Lista törlése",
+  "lists.edit": "Lista szerkesztése",
+  "lists.new.create": "Lista hozzáadása",
+  "lists.new.title_placeholder": "Új lista cím",
+  "lists.search": "Keresés a követtett személyek között",
+  "lists.subheading": "Listáid",
   "loading_indicator.label": "Betöltés...",
-  "media_gallery.toggle_visible": "Toggle visibility",
-  "missing_indicator.label": "Not found",
-  "mute_modal.hide_notifications": "Hide notifications from this user?",
-  "navigation_bar.blocks": "Blocked users",
-  "navigation_bar.community_timeline": "Local timeline",
+  "media_gallery.toggle_visible": "Láthatóság váltása",
+  "missing_indicator.label": "Nincs találat",
+  "missing_indicator.sublabel": "Ezen forrás nem található",
+  "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.edit_profile": "Profil szerkesztése",
-  "navigation_bar.favourites": "Favourites",
-  "navigation_bar.follow_requests": "Follow requests",
-  "navigation_bar.info": "Extended information",
-  "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
-  "navigation_bar.lists": "Lists",
+  "navigation_bar.favourites": "Kedvencek",
+  "navigation_bar.follow_requests": "Követési kérések",
+  "navigation_bar.info": "Ezen szerverről",
+  "navigation_bar.keyboard_shortcuts": "Gyorsbillentyűk",
+  "navigation_bar.lists": "Listák",
   "navigation_bar.logout": "Kijelentkezés",
-  "navigation_bar.mutes": "Muted users",
-  "navigation_bar.pins": "Pinned toots",
+  "navigation_bar.mutes": "Némított felhasználók",
+  "navigation_bar.pins": "Kitűzött tülkök",
   "navigation_bar.preferences": "Beállítások",
   "navigation_bar.public_timeline": "Nyilvános időfolyam",
   "notification.favourite": "{name} kedvencnek jelölte az állapotod",
   "notification.follow": "{name} követ téged",
   "notification.mention": "{name} megemlített",
-  "notification.reblog": "{name} reblogolta az állapotod",
-  "notifications.clear": "Clear notifications",
-  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
-  "notifications.column_settings.alert": "Desktop notifications",
-  "notifications.column_settings.favourite": "Favourites:",
-  "notifications.column_settings.follow": "New followers:",
-  "notifications.column_settings.mention": "Mentions:",
-  "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
-  "notifications.column_settings.reblog": "Boosts:",
-  "notifications.column_settings.show": "Show in column",
-  "notifications.column_settings.sound": "Play sound",
-  "onboarding.done": "Done",
-  "onboarding.next": "Next",
-  "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
-  "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
-  "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
-  "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
-  "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
-  "onboarding.page_one.welcome": "Welcome to Mastodon!",
-  "onboarding.page_six.admin": "Your instance's admin is {admin}.",
-  "onboarding.page_six.almost_done": "Almost done...",
-  "onboarding.page_six.appetoot": "Bon Appetoot!",
-  "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
-  "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
-  "onboarding.page_six.guidelines": "community guidelines",
-  "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
-  "onboarding.page_six.various_app": "mobile apps",
-  "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
+  "notification.reblog": "{name} rebloggolta az állapotod",
+  "notifications.clear": "Értesítések törlése",
+  "notifications.clear_confirmation": "Biztos benne, hogy véglegesen törölni akarja az összes értesítését?",
+  "notifications.column_settings.alert": "Asztali gépi értesítések",
+  "notifications.column_settings.favourite": "Kedvencek:",
+  "notifications.column_settings.follow": "Új követők:",
+  "notifications.column_settings.mention": "Megemítéseim:",
+  "notifications.column_settings.push": "Push értesítések",
+  "notifications.column_settings.push_meta": "Ezen eszköz",
+  "notifications.column_settings.reblog": "Rebloggolások:",
+  "notifications.column_settings.show": "Oszlopban mutatás",
+  "notifications.column_settings.sound": "Hang lejátszása",
+  "onboarding.done": "Befejezve",
+  "onboarding.next": "Következő",
+  "onboarding.page_five.public_timelines": "A helyi idővonal mindenkinek a publikus posztját mutatja a(z) {domain}-n. A federált idővonal mindenki publikus posztját mutatja akit {domain} felhasználói követnek. Ezek a publikus idővonalak, nagyszerű mód új emberek megismerésére.",
+  "onboarding.page_four.home": "A hazai idővonal azon emberek posztjait mutatja akiket te követsz.",
+  "onboarding.page_four.notifications": "Az értesítések oszlop más felhasználók interakcióját veled tükrözi.",
+  "onboarding.page_one.federation": "Mastodon egy független szerverekből alkotott hálózat melyek együttműködése egy nagy szociális hálót képez. Ezeket a szervereket instanciáknak hívjuk.",
+  "onboarding.page_one.full_handle": "Teljes elérhetőséged",
+  "onboarding.page_one.handle_hint": "Ez az amit a barátaidnak mondasz ha meg akarnak keresni.",
+  "onboarding.page_one.welcome": "Üdvözölünk a Mastodon-on!",
+  "onboarding.page_six.admin": "Az instanciád adminisztrátora {admin}.",
+  "onboarding.page_six.almost_done": "Majdnem megvan...",
+  "onboarding.page_six.appetoot": "Bon Appetülk!",
+  "onboarding.page_six.apps_available": "Vannak {apps} iOS-re, Androidra és más platformokra is.",
+  "onboarding.page_six.github": "Mastodon egy szabad és nyílt-forráskódú szoftver. Jelentheted a bug-okat, kérhetsz új funkcionalitásokat vagy hozzájárulhatsz a kódhoz {github}-on.",
+  "onboarding.page_six.guidelines": "közösségi útmutató",
+  "onboarding.page_six.read_guidelines": "Kérjük olvassa el a(z) {domain}-nak a {guidelines}ját!",
+  "onboarding.page_six.various_app": "alkalmazások",
+  "onboarding.page_three.profile": "Módosítsa a profilját, hogy megváltoztassa az avatárt, bio-t vagy nevet. Ott megtalálja a többi beállítást is.",
   "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
   "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
-  "onboarding.skip": "Skip",
-  "privacy.change": "Adjust status privacy",
-  "privacy.direct.long": "Post to mentioned users only",
-  "privacy.direct.short": "Direct",
-  "privacy.private.long": "Post to followers only",
-  "privacy.private.short": "Followers-only",
-  "privacy.public.long": "Post to public timelines",
-  "privacy.public.short": "Public",
+  "onboarding.skip": "Átugrás",
+  "privacy.change": "Státusz láthatóságának módosítása",
+  "privacy.direct.long": "Posztolás csak az említett felhasználóknak",
+  "privacy.direct.short": "Egyenesen",
+  "privacy.private.long": "Posztolás csak követőknek",
+  "privacy.private.short": "Csak követőknek",
+  "privacy.public.long": "Posztolás a publikus idővonalakra",
+  "privacy.public.short": "Publikus",
   "privacy.unlisted.long": "Do not show in public timelines",
-  "privacy.unlisted.short": "Unlisted",
+  "privacy.unlisted.short": "Listázatlan",
+  "regeneration_indicator.label": "Töltődik…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
-  "relative_time.just_now": "now",
+  "relative_time.just_now": "most",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Mégsem",
-  "report.placeholder": "Additional comments",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+  "report.placeholder": "További kommentek",
   "report.submit": "Submit",
   "report.target": "Reporting",
   "search.placeholder": "Keresés",
-  "search_popout.search_format": "Advanced search format",
+  "search_popout.search_format": "Fejlett keresés",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
-  "search_popout.tips.user": "user",
+  "search_popout.tips.user": "felhasználó",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
-  "standalone.public_title": "A look inside...",
+  "standalone.public_title": "Betekintés...",
   "status.block": "Block @{name}",
-  "status.cannot_reblog": "This post cannot be boosted",
+  "status.cannot_reblog": "Ezen státusz nem rebloggolható",
   "status.delete": "Törlés",
-  "status.embed": "Embed",
+  "status.embed": "Beágyaz",
   "status.favourite": "Kedvenc",
-  "status.load_more": "Load more",
-  "status.media_hidden": "Media hidden",
+  "status.load_more": "Többet",
+  "status.media_hidden": "Média elrejtve",
   "status.mention": "Említés",
-  "status.more": "More",
-  "status.mute": "Mute @{name}",
-  "status.mute_conversation": "Mute conversation",
-  "status.open": "Expand this status",
-  "status.pin": "Pin on profile",
+  "status.more": "Többet",
+  "status.mute": "@{name} némítása",
+  "status.mute_conversation": "Beszélgetés némítása",
+  "status.open": "Státusz kinagyítása",
+  "status.pin": "Kitűzés a profilra",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Reblog",
   "status.reblogged_by": "{name} reblogolta",
   "status.reply": "Válasz",
-  "status.replyAll": "Reply to thread",
+  "status.replyAll": "Válaszolj a beszélgetésre",
   "status.report": "Report @{name}",
   "status.sensitive_toggle": "Katt a megtekintéshez",
   "status.sensitive_warning": "Érzékeny tartalom",
-  "status.share": "Share",
-  "status.show_less": "Show less",
-  "status.show_more": "Show more",
-  "status.unmute_conversation": "Unmute conversation",
-  "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Összeállítás",
-  "tabs_bar.federated_timeline": "Federated",
+  "status.share": "Megosztás",
+  "status.show_less": "Kevesebb",
+  "status.show_more": "Többet",
+  "status.unmute_conversation": "Beszélgetés némításának elvonása",
+  "status.unpin": "Kitűzés eltávolítása a profilról",
+  "tabs_bar.federated_timeline": "Federált",
   "tabs_bar.home": "Kezdőlap",
   "tabs_bar.local_timeline": "Local",
-  "tabs_bar.notifications": "Notifications",
-  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
-  "upload_area.title": "Drag & drop to upload",
+  "tabs_bar.notifications": "Értesítések",
+  "ui.beforeunload": "A piszkozata el fog vesztődni ha elhagyja Mastodon-t.",
+  "upload_area.title": "Húzza ide a feltöltéshez",
   "upload_button.label": "Média hozzáadása",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Mégsem",
   "upload_progress.label": "Uploading...",
   "video.close": "Close video",
@@ -256,7 +272,7 @@
   "video.fullscreen": "Full screen",
   "video.hide": "Hide video",
   "video.mute": "Mute sound",
-  "video.pause": "Pause",
-  "video.play": "Play",
-  "video.unmute": "Unmute sound"
+  "video.pause": "Szünet",
+  "video.play": "Lejátszás",
+  "video.unmute": "Hang kinémítása"
 }
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
new file mode 100644
index 000000000..b5e9a2b5a
--- /dev/null
+++ b/app/javascript/mastodon/locales/hy.json
@@ -0,0 +1,278 @@
+{
+  "account.block": "Արգելափակել @{name}֊ին",
+  "account.block_domain": "Թաքցնել ամենը հետեւյալ տիրույթից՝ {domain}",
+  "account.blocked": "Blocked",
+  "account.disclaimer_full": "Ներքոհիշյալը կարող է ոչ ամբողջությամբ արտացոլել օգտատիրոջ էջի տվյալները։",
+  "account.domain_blocked": "Domain hidden",
+  "account.edit_profile": "Խմբագրել անձնական էջը",
+  "account.follow": "Հետեւել",
+  "account.followers": "Հետեւվողներ",
+  "account.follows": "Հետեւում է",
+  "account.follows_you": "Հետեւում է քեզ",
+  "account.hide_reblogs": "Թաքցնել @{name}֊ի տարածածները",
+  "account.media": "Մեդիա",
+  "account.mention": "Նշել @{name}֊ին",
+  "account.moved_to": "{name}֊ը տեղափոխվել է՝",
+  "account.mute": "Լռեցնել @{name}֊ին",
+  "account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
+  "account.muted": "Muted",
+  "account.posts": "Գրառումներ",
+  "account.posts_with_replies": "Toots with replies",
+  "account.report": "Բողոքել @{name}֊ից",
+  "account.requested": "Հաստատման կարիք ունի։ Սեղմիր՝ հետեւելու հայցը չեղարկելու համար։",
+  "account.share": "Կիսվել @{name}֊ի էջով",
+  "account.show_reblogs": "Ցուցադրել @{name}֊ի տարածածները",
+  "account.unblock": "Ապաարգելափակել @{name}֊ին",
+  "account.unblock_domain": "Ցուցադրել {domain} թաքցված տիրույթի գրառումները",
+  "account.unfollow": "Չհետեւել",
+  "account.unmute": "Ապալռեցնել @{name}֊ին",
+  "account.unmute_notifications": "Միացնել ծանուցումները @{name}֊ից",
+  "account.view_full_profile": "Դիտել ամբողջական տարբերակը։",
+  "boost_modal.combo": "Կարող ես սեղմել {combo}՝ սա հաջորդ անգամ բաց թողնելու համար",
+  "bundle_column_error.body": "Այս բաղադրիչը բեռնելու ընթացքում ինչ֊որ բան խափանվեց։",
+  "bundle_column_error.retry": "Կրկին փորձել",
+  "bundle_column_error.title": "Ցանցային սխալ",
+  "bundle_modal_error.close": "Փակել",
+  "bundle_modal_error.message": "Այս բաղադրիչը բեռնելու ընթացքում ինչ֊որ բան խափանվեց։",
+  "bundle_modal_error.retry": "Կրկին փորձել",
+  "column.blocks": "Արգելափակված օգտատերեր",
+  "column.community": "Տեղական հոսք",
+  "column.favourites": "Հավանածներ",
+  "column.follow_requests": "Հետեւելու հայցեր",
+  "column.home": "Հիմնական",
+  "column.lists": "Ցանկեր",
+  "column.mutes": "Լռեցրած օգտատերեր",
+  "column.notifications": "Ծանուցումներ",
+  "column.pins": "Ամրացված թթեր",
+  "column.public": "Դաշնային հոսք",
+  "column_back_button.label": "Ետ",
+  "column_header.hide_settings": "Թաքցնել կարգավորումները",
+  "column_header.moveLeft_settings": "Տեղաշարժել սյունը ձախ",
+  "column_header.moveRight_settings": "Տեղաշարժել սյունը աջ",
+  "column_header.pin": "Ամրացնել",
+  "column_header.show_settings": "Ցուցադրել կարգավորումները",
+  "column_header.unpin": "Հանել",
+  "column_subheading.navigation": "Նավարկություն",
+  "column_subheading.settings": "Կարգավորումներ",
+  "compose_form.hashtag_warning": "Այս թութը չի հաշվառվի որեւէ պիտակի տակ, քանզի այն ծածուկ է։ Միայն հրապարակային թթերը հնարավոր է որոնել պիտակներով։",
+  "compose_form.lock_disclaimer": "Քո հաշիվը {locked} չէ։ Յուրաքանչյուր ոք կարող է հետեւել քեզ եւ տեսնել միայն հետեւողների համար նախատեսված գրառումները։",
+  "compose_form.lock_disclaimer.lock": "փակ",
+  "compose_form.placeholder": "Ի՞նչ կա մտքիդ",
+  "compose_form.publish": "Թթել",
+  "compose_form.publish_loud": "Թթե՜լ",
+  "compose_form.sensitive": "Նշել բովանդակությունը որպես կասկածելի",
+  "compose_form.spoiler": "Թաքցնել տեքստը նախազգուշացման ետեւում",
+  "compose_form.spoiler_placeholder": "Գրիր նախազգուշացումդ այստեղ",
+  "confirmation_modal.cancel": "Չեղարկել",
+  "confirmations.block.confirm": "Արգելափակել",
+  "confirmations.block.message": "Վստա՞հ ես, որ ուզում ես արգելափակել {name}֊ին։",
+  "confirmations.delete.confirm": "Ջնջել",
+  "confirmations.delete.message": "Վստա՞հ ես, որ ուզում ես ջնջել այս թութը։",
+  "confirmations.delete_list.confirm": "Ջնջել",
+  "confirmations.delete_list.message": "Վստա՞հ ես, որ ուզում ես մշտապես ջնջել այս ցանկը։",
+  "confirmations.domain_block.confirm": "Թաքցնել ամբողջ տիրույթը",
+  "confirmations.domain_block.message": "Հաստատ֊հաստա՞տ վստահ ես, որ ուզում ես արգելափակել ամբողջ {domain} տիրույթը։ Սովորաբար մի երկու թիրախավորված արգելափակում կամ լռեցում բավական է ու նախընտրելի։",
+  "confirmations.mute.confirm": "Լռեցնել",
+  "confirmations.mute.message": "Վստա՞հ ես, որ ուզում ես {name}֊ին լռեցնել։",
+  "confirmations.unfollow.confirm": "Ապահետեւել",
+  "confirmations.unfollow.message": "Վստա՞հ ես, որ ուզում ես այլեւս չհետեւել {name}֊ին։",
+  "embed.instructions": "Այս թութը քո կայքում ներդնելու համար կարող ես պատճենել ներքոհիշյալ կոդը։",
+  "embed.preview": "Ահա, թե ինչ տեսք կունենա այն՝",
+  "emoji_button.activity": "Զբաղմունքներ",
+  "emoji_button.custom": "Հատուկ",
+  "emoji_button.flags": "Դրոշներ",
+  "emoji_button.food": "Կերուխում",
+  "emoji_button.label": "Էմոջի ավելացնել",
+  "emoji_button.nature": "Բնություն",
+  "emoji_button.not_found": "Նման էմոջիներ դեռ չեն հայտնաբերվել։ (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Առարկաներ",
+  "emoji_button.people": "Մարդիկ",
+  "emoji_button.recent": "Հաճախ օգտագործվող",
+  "emoji_button.search": "Որոնել…",
+  "emoji_button.search_results": "Որոնման արդյունքներ",
+  "emoji_button.symbols": "Նշաններ",
+  "emoji_button.travel": "Ուղեւորություն եւ տեղանքներ",
+  "empty_column.community": "Տեղական հոսքը դատա՛րկ է։ Հրապարակային մի բան գրիր շարժիչը խոդ տալու համար։",
+  "empty_column.hashtag": "Այս պիտակով դեռ ոչինչ չկա։",
+  "empty_column.home": "Քո հիմնական հոսքը դատա՛րկ է։ Այցելի՛ր {public}ը կամ օգտվիր որոնումից՝ այլ մարդկանց հանդիպելու համար։",
+  "empty_column.home.public_timeline": "հրապարակային հոսք",
+  "empty_column.list": "Այս ցանկում դեռ ոչինչ չկա։ Երբ ցանկի անդամներից որեւէ մեկը նոր թութ գրի, այն կհայտնվի այստեղ։",
+  "empty_column.notifications": "Ոչ մի ծանուցում դեռ չունես։ Բզիր մյուսներին՝ խոսակցությունը սկսելու համար։",
+  "empty_column.public": "Այստեղ բան չկա՛։ Հրապարակային մի բան գրիր կամ հետեւիր այլ հանգույցներից էակների՝ այն լցնելու համար։",
+  "follow_request.authorize": "Վավերացնել",
+  "follow_request.reject": "Մերժել",
+  "getting_started.appsshort": "Հավելվածներ",
+  "getting_started.faq": "ՀՏՀ",
+  "getting_started.heading": "Ինչպես սկսել",
+  "getting_started.open_source_notice": "Մաստոդոնը բաց ելատեքստով ծրագրակազմ է։ Կարող ես ներդրում անել կամ վրեպներ զեկուցել ԳիթՀաբում՝ {github}։",
+  "getting_started.userguide": "Ձեռնարկ",
+  "home.column_settings.advanced": "Առաջադեմ",
+  "home.column_settings.basic": "Հիմնական",
+  "home.column_settings.filter_regex": "Զտել օրինաչափ արտահայտությամբ",
+  "home.column_settings.show_reblogs": "Ցուցադրել տարածածները",
+  "home.column_settings.show_replies": "Ցուցադրել պատասխանները",
+  "home.settings": "Սյան կարգավորումներ",
+  "keyboard_shortcuts.back": "ետ նավարկելու համար",
+  "keyboard_shortcuts.boost": "տարածելու համար",
+  "keyboard_shortcuts.column": "սյուներից մեկի վրա սեւեռվելու համար",
+  "keyboard_shortcuts.compose": "շարադրման տիրույթին սեւեռվելու համար",
+  "keyboard_shortcuts.description": "Նկարագրություն",
+  "keyboard_shortcuts.down": "ցանկով ներքեւ շարժվելու համար",
+  "keyboard_shortcuts.enter": "թութը բացելու համար",
+  "keyboard_shortcuts.favourite": "հավանելու համար",
+  "keyboard_shortcuts.heading": "Ստեղնաշարի կարճատներ",
+  "keyboard_shortcuts.hotkey": "Հատուկ ստեղն",
+  "keyboard_shortcuts.legend": "այս ձեռնարկը ցուցադրելու համար",
+  "keyboard_shortcuts.mention": "հեղինակին նշելու համար",
+  "keyboard_shortcuts.reply": "պատասխանելու համար",
+  "keyboard_shortcuts.search": "որոնման դաշտին սեւեռվելու համար",
+  "keyboard_shortcuts.toot": "թարմ թութ սկսելու համար",
+  "keyboard_shortcuts.unfocus": "տեքստի/որոնման տիրույթից ապասեւեռվելու համար",
+  "keyboard_shortcuts.up": "ցանկով վերեւ շարժվելու համար",
+  "lightbox.close": "Փակել",
+  "lightbox.next": "Հաջորդ",
+  "lightbox.previous": "Նախորդ",
+  "lists.account.add": "Ավելացնել ցանկին",
+  "lists.account.remove": "Հանել ցանկից",
+  "lists.delete": "Ջնջել ցանկը",
+  "lists.edit": "Փոփոխել ցանկը",
+  "lists.new.create": "Ավելացնել ցանկ",
+  "lists.new.title_placeholder": "Նոր ցանկի վերնագիր",
+  "lists.search": "Փնտրել քո հետեւած մարդկանց մեջ",
+  "lists.subheading": "Քո ցանկերը",
+  "loading_indicator.label": "Բեռնվում է…",
+  "media_gallery.toggle_visible": "Ցուցադրել/թաքցնել",
+  "missing_indicator.label": "Չգտնվեց",
+  "missing_indicator.sublabel": "This resource could not be found",
+  "mute_modal.hide_notifications": "Թաքցնե՞լ ցանուցումներն այս օգտատիրոջից։",
+  "navigation_bar.blocks": "Արգելափակված օգտատերեր",
+  "navigation_bar.community_timeline": "Տեղական հոսք",
+  "navigation_bar.edit_profile": "Խմբագրել անձնական էջը",
+  "navigation_bar.favourites": "Հավանածներ",
+  "navigation_bar.follow_requests": "Հետեւելու հայցեր",
+  "navigation_bar.info": "Այս հանգույցի մասին",
+  "navigation_bar.keyboard_shortcuts": "Ստեղնաշարի կարճատներ",
+  "navigation_bar.lists": "Ցանկեր",
+  "navigation_bar.logout": "Դուրս գալ",
+  "navigation_bar.mutes": "Լռեցրած օգտատերեր",
+  "navigation_bar.pins": "Ամրացված թթեր",
+  "navigation_bar.preferences": "Նախապատվություններ",
+  "navigation_bar.public_timeline": "Դաշնային հոսք",
+  "notification.favourite": "{name} հավանեց թութդ",
+  "notification.follow": "{name} սկսեց հետեւել քեզ",
+  "notification.mention": "{name} նշեց քեզ",
+  "notification.reblog": "{name} տարածեց թութդ",
+  "notifications.clear": "Մաքրել ծանուցումները",
+  "notifications.clear_confirmation": "Վստա՞հ ես, որ ուզում ես մշտապես մաքրել քո բոլոր ծանուցումները։",
+  "notifications.column_settings.alert": "Աշխատատիրույթի ծանուցումներ",
+  "notifications.column_settings.favourite": "Հավանածներից՝",
+  "notifications.column_settings.follow": "Նոր հետեւողներ՝",
+  "notifications.column_settings.mention": "Նշումներ՝",
+  "notifications.column_settings.push": "Հրելու ծանուցումներ",
+  "notifications.column_settings.push_meta": "Այս սարքը",
+  "notifications.column_settings.reblog": "Տարածածներից՝",
+  "notifications.column_settings.show": "Ցուցադրել սյունում",
+  "notifications.column_settings.sound": "Ձայն հանել",
+  "onboarding.done": "Պատրաստ է",
+  "onboarding.next": "Հաջորդ",
+  "onboarding.page_five.public_timelines": "Տեղական հոսքը ցույց է տալիս {domain} տիրույթից բոլորի հրապարակային թթերը։ Դաշնային հոսքը ցույց է տալիս հրապարակային թթերը բոլորից, ում {domain} տիրույթի մարդիկ հետեւում են։ Սրանք Հրապարակային հոսքերն են՝ նոր մարդկանց բացահայտելու հրաշալի միջոց։",
+  "onboarding.page_four.home": "Հիմնական հոսքը ցույց է տալիս այն մարդկանց թթերը, ում հետեւում ես։",
+  "onboarding.page_four.notifications": "Ծանուցումների սյունը ցույց է տալիս, երբ որեւէ մեկը փոխգործակցում է հետդ։",
+  "onboarding.page_one.federation": "Մաստոդոնը անկախ սպասարկիչների ցանց է, որոնք միասնական սոցիալական ցանց են կազմում։ Մենք կոչում ենք այդ սպասարկիչները հանգույցներ։",
+  "onboarding.page_one.full_handle": "Քո ամբողջական օգտանունը",
+  "onboarding.page_one.handle_hint": "Սա այն է, ինչ ասելու ես ընկերներիդ՝ քեզ փնտրելու համար։",
+  "onboarding.page_one.welcome": "Բարի գալուստ Մաստոդո՜ն",
+  "onboarding.page_six.admin": "Քո հանգույցի ադմինը նա է՝ {admin}։",
+  "onboarding.page_six.almost_done": "Գրեթե պատրաստ է…",
+  "onboarding.page_six.appetoot": "Հաջողութությո՜ւն",
+  "onboarding.page_six.apps_available": "Նաեւ կան այՕՍի, Անդրոիդի եւ այլ հարթակների համար {apps}։",
+  "onboarding.page_six.github": "Մաստոդոնն ազատ ու բաց ելատեքստով ծրագրակազմ է։ Կարող ես վրեպներ զեկուցել, նոր հատկություններ հայցել կամ ներդրում անել {github}֊ում։",
+  "onboarding.page_six.guidelines": "համայնքի կանոնակարգ",
+  "onboarding.page_six.read_guidelines": "Խնդրում ենք, կարդա {domain} տիրույթի {guidelines}ը։",
+  "onboarding.page_six.various_app": "հավելվածներ",
+  "onboarding.page_three.profile": "Թարմացրու անձնական էջդ՝ նկարդ, կենսագրությունդ ու անունդ փոխելու համար։ Այնտեղ նաեւ այլ նախապատվություններ կգտնես։",
+  "onboarding.page_three.search": "Օգտվիր որոնման դաշտից՝ մարդկանց գտնելու կամ պիտակներին՝ օրինակ {illustration} ու {introductions}, ծանոթանալու համար։ Ոչ այս հանգույցի բնակիչներին փնտրելու համար օգտագործիր նրանց ամբողջական օգտանունը։",
+  "onboarding.page_two.compose": "Գրիր թթերդ շարադրման սյունակում։ Կարող ես նկարներ վերբեռնել, փոփոխել գաղտնիության կարգավորումները եւ բովանդակության վերաբերյալ նախազգուշացումներ ավելացնել՝ օգտվելով ներքեւի պատկերակներից։",
+  "onboarding.skip": "Բաց թողնել",
+  "privacy.change": "Կարգավորել թթի գաղտնիությունը",
+  "privacy.direct.long": "Թթել միայն նշված օգտատերերի համար",
+  "privacy.direct.short": "Հասցեագրված",
+  "privacy.private.long": "Թթել միայն հետեւողների համար",
+  "privacy.private.short": "Միայն հետեւողներին",
+  "privacy.public.long": "Թթել հրապարակային հոսքերում",
+  "privacy.public.short": "Հրապարակային",
+  "privacy.unlisted.long": "Չթթել հրապարակային հոսքերում",
+  "privacy.unlisted.short": "Ծածուկ",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
+  "relative_time.days": "{number}օր",
+  "relative_time.hours": "{number}ժ",
+  "relative_time.just_now": "նոր",
+  "relative_time.minutes": "{number}ր",
+  "relative_time.seconds": "{number}վ",
+  "reply_indicator.cancel": "Չեղարկել",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+  "report.placeholder": "Լրացուցիչ մեկնաբանություններ",
+  "report.submit": "Ուղարկել",
+  "report.target": "Բողոքել {target}֊ի մասին",
+  "search.placeholder": "Փնտրել",
+  "search_popout.search_format": "Փնտրելու առաջադեմ ձեւ",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "պիտակ",
+  "search_popout.tips.status": "թութ",
+  "search_popout.tips.text": "Հասարակ տեքստը կվերադարձնի համընկնող անուններ, օգտանուններ ու պիտակներ",
+  "search_popout.tips.user": "օգտատեր",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
+  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "standalone.public_title": "Այս պահին…",
+  "status.block": "Արգելափակել @{name}֊ին",
+  "status.cannot_reblog": "Այս թութը չի կարող տարածվել",
+  "status.delete": "Ջնջել",
+  "status.embed": "Ներդնել",
+  "status.favourite": "Հավանել",
+  "status.load_more": "Բեռնել ավելին",
+  "status.media_hidden": "մեդիաբովանդակությունը թաքցված է",
+  "status.mention": "Նշել @{name}֊ին",
+  "status.more": "Ավելին",
+  "status.mute": "Լռեցնել @{name}֊ին",
+  "status.mute_conversation": "Լռեցնել խոսակցությունը",
+  "status.open": "Ընդարձակել այս թութը",
+  "status.pin": "Ամրացնել անձնական էջում",
+  "status.pinned": "Pinned toot",
+  "status.reblog": "Տարածել",
+  "status.reblogged_by": "{name} տարածել է",
+  "status.reply": "Պատասխանել",
+  "status.replyAll": "Պատասխանել թելին",
+  "status.report": "Բողոքել @{name}֊ից",
+  "status.sensitive_toggle": "Կտացրու՝ դիտելու համար",
+  "status.sensitive_warning": "Կասկածելի բովանդակություն",
+  "status.share": "Կիսվել",
+  "status.show_less": "Պակաս",
+  "status.show_more": "Ավելին",
+  "status.unmute_conversation": "Ապալռեցնել խոսակցությունը",
+  "status.unpin": "Հանել անձնական էջից",
+  "tabs_bar.federated_timeline": "Դաշնային",
+  "tabs_bar.home": "Հիմնական",
+  "tabs_bar.local_timeline": "Տեղական",
+  "tabs_bar.notifications": "Ծանուցումներ",
+  "ui.beforeunload": "Քո սեւագիրը կկորի, եթե լքես Մաստոդոնը։",
+  "upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար",
+  "upload_button.label": "Ավելացնել մեդիա",
+  "upload_form.description": "Նկարագրություն ավելացրու տեսողական խնդիրներ ունեցողների համար",
+  "upload_form.focus": "Crop",
+  "upload_form.undo": "Հետարկել",
+  "upload_progress.label": "Վերբեռնվում է…",
+  "video.close": "Փակել  տեսագրությունը",
+  "video.exit_fullscreen": "Անջատել լիաէկրան դիտումը",
+  "video.expand": "Ընդարձակել տեսագրությունը",
+  "video.fullscreen": "Լիաէկրան",
+  "video.hide": "Թաքցնել տեսագրությունը",
+  "video.mute": "Լռեցնել ձայնը",
+  "video.pause": "Դադար տալ",
+  "video.play": "Նվագել",
+  "video.unmute": "Միացնել ձայնը"
+}
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 0942bc33c..596415cde 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Blokir @{name}",
   "account.block_domain": "Hide everything from {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Ubah profil",
   "account.follow": "Ikuti",
   "account.followers": "Pengikut",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "Bisukan @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
   "account.posts": "Postingan",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Laporkan @{name}",
   "account.requested": "Menunggu persetujuan",
   "account.share": "Share @{name}'s profile",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Tunggu sebentar...",
   "media_gallery.toggle_visible": "Tampil/Sembunyikan",
   "missing_indicator.label": "Tidak ditemukan",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Pengguna diblokir",
   "navigation_bar.community_timeline": "Linimasa lokal",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "Linimasa beranda menampilkan postingan dari orang-orang yang anda ikuti.",
   "onboarding.page_four.notifications": "Kolom notifikasi menampilkan ketika seseorang berinteraksi dengan anda.",
   "onboarding.page_one.federation": "Mastodon adalah jaringan dari beberapa server independen yang bergabung untuk membuat jejaring sosial yang besar.",
-  "onboarding.page_one.handle": "Ada berada dalam {domain}, jadi nama user lengkap anda adalah {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Selamat datang di Mastodon!",
   "onboarding.page_six.admin": "Admin serveer anda adalah {admin}.",
   "onboarding.page_six.almost_done": "Hampir selesei...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Publik",
   "privacy.unlisted.long": "Tidak ditampilkan di linimasa publik",
   "privacy.unlisted.short": "Tak Terdaftar",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Batal",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Komentar tambahan",
   "report.submit": "Kirim",
   "report.target": "Melaporkan",
   "search.placeholder": "Pencarian",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count} {count, plural, one {hasil} other {hasil}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Mute conversation",
   "status.open": "Tampilkan status ini",
   "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Boost",
   "status.reblogged_by": "di-boost {name}",
   "status.reply": "Balas",
@@ -239,7 +255,6 @@
   "status.show_more": "Tampilkan semua",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Tulis",
   "tabs_bar.federated_timeline": "Gabungan",
   "tabs_bar.home": "Beranda",
   "tabs_bar.local_timeline": "Lokal",
@@ -248,6 +263,7 @@
   "upload_area.title": "Seret & lepaskan untuk mengunggah",
   "upload_button.label": "Tambahkan media",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Undo",
   "upload_progress.label": "Mengunggah...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index cfd8e299f..4f554b08f 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Blokusar @{name}",
   "account.block_domain": "Hide everything from {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Modifikar profilo",
   "account.follow": "Sequar",
   "account.followers": "Sequanti",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "Celar @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
   "account.posts": "Mesaji",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Denuncar @{name}",
   "account.requested": "Vartante aprobo",
   "account.share": "Share @{name}'s profile",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Kargante...",
   "media_gallery.toggle_visible": "Chanjar videbleso",
   "missing_indicator.label": "Ne trovita",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blokusita uzeri",
   "navigation_bar.community_timeline": "Lokala tempolineo",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
   "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
   "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
-  "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Welcome to Mastodon!",
   "onboarding.page_six.admin": "Your instance's admin is {admin}.",
   "onboarding.page_six.almost_done": "Almost done...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Publike",
   "privacy.unlisted.long": "Ne montrar en publika tempolinei",
   "privacy.unlisted.short": "Ne enlistigota",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Nihiligar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Plusa komenti",
   "report.submit": "Sendar",
   "report.target": "Denuncante",
   "search.placeholder": "Serchez",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Mute conversation",
   "status.open": "Detaligar ca mesajo",
   "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Repetar",
   "status.reblogged_by": "{name} repetita",
   "status.reply": "Respondar",
@@ -239,7 +255,6 @@
   "status.show_more": "Montrar plue",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Kompozar",
   "tabs_bar.federated_timeline": "Federata",
   "tabs_bar.home": "Hemo",
   "tabs_bar.local_timeline": "Lokala",
@@ -248,6 +263,7 @@
   "upload_area.title": "Tranar faligar por kargar",
   "upload_button.label": "Adjuntar kontenajo",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Desfacar",
   "upload_progress.label": "Kargante...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index e14fa410c..6b2532512 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Blocca @{name}",
   "account.block_domain": "Hide everything from {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Modifica profilo",
   "account.follow": "Segui",
   "account.followers": "Seguaci",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "Silenzia @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
   "account.posts": "Posts",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Segnala @{name}",
   "account.requested": "In attesa di approvazione",
   "account.share": "Share @{name}'s profile",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Carico...",
   "media_gallery.toggle_visible": "Imposta visibilità",
   "missing_indicator.label": "Non trovato",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Utenti bloccati",
   "navigation_bar.community_timeline": "Timeline locale",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
   "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
   "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
-  "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Welcome to Mastodon!",
   "onboarding.page_six.admin": "Your instance's admin is {admin}.",
   "onboarding.page_six.almost_done": "Almost done...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Pubblico",
   "privacy.unlisted.long": "Non mostrare sulla timeline pubblica",
   "privacy.unlisted.short": "Non elencato",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Annulla",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Commenti aggiuntivi",
   "report.submit": "Invia",
   "report.target": "Invio la segnalazione",
   "search.placeholder": "Cerca",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Mute conversation",
   "status.open": "Espandi questo post",
   "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Condividi",
   "status.reblogged_by": "{name} ha condiviso",
   "status.reply": "Rispondi",
@@ -239,7 +255,6 @@
   "status.show_more": "Mostra di più",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Scrivi",
   "tabs_bar.federated_timeline": "Federazione",
   "tabs_bar.home": "Home",
   "tabs_bar.local_timeline": "Locale",
@@ -248,6 +263,7 @@
   "upload_area.title": "Trascina per caricare",
   "upload_button.label": "Aggiungi file multimediale",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Annulla",
   "upload_progress.label": "Sto caricando...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 19885056c..04374abc3 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -1,7 +1,9 @@
 {
-  "account.block": "ブロック",
+  "account.block": "@{name}さんをブロック",
   "account.block_domain": "{domain}全体を非表示",
+  "account.blocked": "ブロック済み",
   "account.disclaimer_full": "以下の情報は不正確な可能性があります。",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "プロフィールを編集",
   "account.follow": "フォロー",
   "account.followers": "フォロワー",
@@ -9,22 +11,24 @@
   "account.follows_you": "フォローされています",
   "account.hide_reblogs": "@{name}さんからのブーストを非表示",
   "account.media": "メディア",
-  "account.mention": "返信",
+  "account.mention": "@{name}さんにトゥート",
   "account.moved_to": "{name}さんは引っ越しました:",
-  "account.mute": "ミュート",
-  "account.mute_notifications": "@{name}さんからの通知を受け取る",
+  "account.mute": "@{name}さんをミュート",
+  "account.mute_notifications": "@{name}さんからの通知を受け取らない",
+  "account.muted": "ミュート済み",
   "account.posts": "投稿",
-  "account.report": "通報",
-  "account.requested": "承認待ち",
+  "account.posts_with_replies": "投稿と返信",
+  "account.report": "@{name}さんを通報",
+  "account.requested": "フォロー承認待ちです。クリックしてキャンセル",
   "account.share": "@{name}さんのプロフィールを共有する",
   "account.show_reblogs": "@{name}さんからのブーストを表示",
-  "account.unblock": "ブロック解除",
+  "account.unblock": "@{name}さんのブロックを解除",
   "account.unblock_domain": "{domain}を表示",
   "account.unfollow": "フォロー解除",
-  "account.unmute": "ミュート解除",
-  "account.unmute_notifications": "@{name}さんからの通知を受け取らない",
+  "account.unmute": "@{name}さんのミュートを解除",
+  "account.unmute_notifications": "@{name}さんからの通知を受け取る",
   "account.view_full_profile": "全ての情報を見る",
-  "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます",
+  "boost_modal.combo": "次からは{combo}を押せばスキップできます",
   "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。",
   "bundle_column_error.retry": "再試行",
   "bundle_column_error.title": "ネットワークエラー",
@@ -69,13 +73,13 @@
   "confirmations.delete.confirm": "削除",
   "confirmations.delete.message": "本当に削除しますか?",
   "confirmations.delete_list.confirm": "削除",
-  "confirmations.delete_list.message": "本当に削除しますか?",
+  "confirmations.delete_list.message": "本当にこのリストを完全に削除しますか?",
   "confirmations.domain_block.confirm": "ドメイン全体を非表示",
-  "confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。",
+  "confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。",
   "confirmations.mute.confirm": "ミュート",
   "confirmations.mute.message": "本当に{name}さんをミュートしますか?",
   "confirmations.unfollow.confirm": "フォロー解除",
-  "confirmations.unfollow.message": "本当に{name}さんをフォロー解除しますか?",
+  "confirmations.unfollow.message": "本当に{name}さんのフォローを解除しますか?",
   "embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。",
   "embed.preview": "表示例:",
   "emoji_button.activity": "活動",
@@ -84,7 +88,7 @@
   "emoji_button.food": "食べ物",
   "emoji_button.label": "絵文字を追加",
   "emoji_button.nature": "自然",
-  "emoji_button.not_found": "絵文字がない!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.not_found": "絵文字がなーい!! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "物",
   "emoji_button.people": "人々",
   "emoji_button.recent": "よく使う絵文字",
@@ -107,7 +111,7 @@
   "getting_started.heading": "スタート",
   "getting_started.open_source_notice": "Mastodonはオープンソースソフトウェアです。誰でもGitHub({github})から開発に参加したり、問題を報告したりできます。",
   "getting_started.userguide": "ユーザーガイド",
-  "home.column_settings.advanced": "上級者向け",
+  "home.column_settings.advanced": "高度な設定",
   "home.column_settings.basic": "基本設定",
   "home.column_settings.filter_regex": "正規表現でフィルター",
   "home.column_settings.show_reblogs": "ブースト表示",
@@ -144,6 +148,7 @@
   "loading_indicator.label": "読み込み中...",
   "media_gallery.toggle_visible": "表示切り替え",
   "missing_indicator.label": "見つかりません",
+  "missing_indicator.sublabel": "見つかりませんでした",
   "mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?",
   "navigation_bar.blocks": "ブロックしたユーザー",
   "navigation_bar.community_timeline": "ローカルタイムライン",
@@ -156,7 +161,7 @@
   "navigation_bar.lists": "リスト",
   "navigation_bar.logout": "ログアウト",
   "navigation_bar.mutes": "ミュートしたユーザー",
-  "navigation_bar.pins": "固定されたトゥート",
+  "navigation_bar.pins": "固定したトゥート",
   "navigation_bar.preferences": "ユーザー設定",
   "navigation_bar.public_timeline": "連合タイムライン",
   "navigation_bar.misc": "その他",
@@ -181,7 +186,8 @@
   "onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。",
   "onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。",
   "onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。",
-  "onboarding.page_one.handle": "今あなたは数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です",
+  "onboarding.page_one.full_handle": "あなたのフルハンドル",
+  "onboarding.page_one.handle_hint": "あなたを探している友達に伝えるといいでしょう。",
   "onboarding.page_one.welcome": "Mastodonへようこそ!",
   "onboarding.page_six.admin": "あなたのインスタンスの管理者は{admin}です。",
   "onboarding.page_six.almost_done": "以上です。",
@@ -189,11 +195,11 @@
   "onboarding.page_six.apps_available": "iOS、Androidあるいは他のプラットフォームで使える{apps}があります。",
   "onboarding.page_six.github": "MastodonはOSSです。バグ報告や機能要望あるいは貢献を{github}から行なえます。",
   "onboarding.page_six.guidelines": "コミュニティガイドライン",
-  "onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください!",
-  "onboarding.page_six.various_app": "様々なモバイルアプリ",
+  "onboarding.page_six.read_guidelines": "{domain}の{guidelines}を読むことを忘れないようにしてください!",
+  "onboarding.page_six.various_app": "モバイルアプリ",
   "onboarding.page_three.profile": "「プロフィールを編集」から、あなたの自己紹介や表示名を変更できます。またそこでは他の設定ができます。",
   "onboarding.page_three.search": "検索バーで、{illustration}や{introductions}のように特定のハッシュタグの投稿を見たり、ユーザーを探したりできます。",
-  "onboarding.page_two.compose": "フォームから投稿できます。イメージや、公開範囲の設定や、表示時の警告の設定は下部のアイコンから行なえます。",
+  "onboarding.page_two.compose": "フォームから投稿できます。イメージや、公開範囲の設定や、表示時の警告の設定は下部のアイコンから行えます。",
   "onboarding.skip": "スキップ",
   "privacy.change": "投稿のプライバシーを変更",
   "privacy.direct.long": "メンションしたユーザーだけに公開",
@@ -204,41 +210,51 @@
   "privacy.public.short": "公開",
   "privacy.unlisted.long": "公開TLで表示しない",
   "privacy.unlisted.short": "未収載",
+  "regeneration_indicator.label": "読み込み中…",
+  "regeneration_indicator.sublabel": "ホームタイムラインは準備中です!",
   "relative_time.days": "{number}日前",
   "relative_time.hours": "{number}時間前",
   "relative_time.just_now": "今",
   "relative_time.minutes": "{number}分前",
   "relative_time.seconds": "{number}秒前",
   "reply_indicator.cancel": "キャンセル",
-  "report.placeholder": "コメント",
+  "report.forward": "{target} に転送する",
+  "report.forward_hint": "このアカウントは別のインスタンスに所属しています。通報内容を匿名で転送しますか?",
+  "report.hint": "通報内容はあなたのインスタンスのモデレーターへ送信されます。通報理由を入力してください。:",
+  "report.placeholder": "追加コメント",
   "report.submit": "通報する",
-  "report.target": "{target} を通報する",
+  "report.target": "{target}さんを通報する",
   "search.placeholder": "検索",
   "search_popout.search_format": "高度な検索フォーマット",
+  "search_popout.tips.full_text": "表示名やユーザー名、ハッシュタグのほか、あなたのトゥートやお気に入り、ブーストしたトゥート、返信に一致する単純なテキスト。",
   "search_popout.tips.hashtag": "ハッシュタグ",
   "search_popout.tips.status": "トゥート",
   "search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト",
   "search_popout.tips.user": "ユーザー",
+  "search_results.accounts": "人々",
+  "search_results.hashtags": "ハッシュタグ",
+  "search_results.statuses": "トゥート",
   "search_results.total": "{count, number}件の結果",
   "standalone.public_title": "今こんな話をしています...",
-  "status.block": "@{name}をブロック",
+  "status.block": "@{name}さんをブロック",
   "status.cannot_reblog": "この投稿はブーストできません",
   "status.delete": "削除",
   "status.embed": "埋め込み",
   "status.favourite": "お気に入り",
   "status.load_more": "もっと見る",
   "status.media_hidden": "非表示のメディア",
-  "status.mention": "返信",
+  "status.mention": "@{name}さんにトゥート",
   "status.more": "もっと見る",
-  "status.mute": "@{name}をミュート",
+  "status.mute": "@{name}さんをミュート",
   "status.mute_conversation": "会話をミュート",
   "status.open": "詳細を表示",
   "status.pin": "プロフィールに固定表示",
+  "status.pinned": "固定されたトゥート",
   "status.reblog": "ブースト",
-  "status.reblogged_by": "{name}さんにブーストされました",
+  "status.reblogged_by": "{name}さんがブースト",
   "status.reply": "返信",
   "status.replyAll": "全員に返信",
-  "status.report": "通報",
+  "status.report": "@{name}さんを通報",
   "status.sensitive_toggle": "クリックして表示",
   "status.sensitive_warning": "閲覧注意",
   "status.share": "共有",
@@ -246,15 +262,15 @@
   "status.show_more": "もっと見る",
   "status.unmute_conversation": "会話のミュートを解除",
   "status.unpin": "プロフィールの固定表示を解除",
-  "tabs_bar.compose": "投稿",
   "tabs_bar.federated_timeline": "連合",
   "tabs_bar.home": "ホーム",
   "tabs_bar.local_timeline": "ローカル",
   "tabs_bar.notifications": "通知",
-  "ui.beforeunload": "Mastodonから離れるとあなたのドラフトは失われます。",
+  "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。",
   "upload_area.title": "ドラッグ&ドロップでアップロード",
   "upload_button.label": "メディアを追加",
   "upload_form.description": "視覚障害者のための説明",
+  "upload_form.focus": "焦点",
   "upload_form.undo": "やり直す",
   "upload_progress.label": "アップロード中...",
   "video.close": "動画を閉じる",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 31e5e377e..05c84fd37 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -1,7 +1,9 @@
 {
-  "account.block": "차단",
+  "account.block": "@{name}을 차단",
   "account.block_domain": "{domain} 전체를 숨김",
+  "account.blocked": "차단 됨",
   "account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.",
+  "account.domain_blocked": "도메인 숨겨짐",
   "account.edit_profile": "프로필 편집",
   "account.follow": "팔로우",
   "account.followers": "팔로워",
@@ -11,11 +13,13 @@
   "account.media": "미디어",
   "account.mention": "답장",
   "account.moved_to": "{name}는 계정을 이동했습니다:",
-  "account.mute": "뮤트",
+  "account.mute": "@{name} 뮤트",
   "account.mute_notifications": "@{name}의 알림을 뮤트",
-  "account.posts": "포스트",
-  "account.report": "신고",
-  "account.requested": "승인 대기 중",
+  "account.muted": "뮤트 됨",
+  "account.posts": "게시물",
+  "account.posts_with_replies": "툿과 답장",
+  "account.report": "@{name} 신고",
+  "account.requested": "승인 대기 중. 클릭해서 취소하기",
   "account.share": "@{name}의 프로파일 공유",
   "account.show_reblogs": "@{name}의 부스트 보기",
   "account.unblock": "차단 해제",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "불러오는 중...",
   "media_gallery.toggle_visible": "표시 전환",
   "missing_indicator.label": "찾을 수 없습니다",
+  "missing_indicator.sublabel": "이 리소스를 찾을 수 없었습니다",
   "mute_modal.hide_notifications": "이 사용자로부터의 알림을 뮤트하시겠습니까?",
   "navigation_bar.blocks": "차단한 사용자",
   "navigation_bar.community_timeline": "로컬 타임라인",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "홈 타임라인에서는 내가 팔로우 중인 사람들의 포스트를 표시합니다.",
   "onboarding.page_four.notifications": "알림에서는 다른 사람들과의 연결을 표시합니다.",
   "onboarding.page_one.federation": "Mastodon은 누구나 참가할 수 있는 SNS입니다.",
-  "onboarding.page_one.handle": "여러분은 지금 수많은 Mastodon 인스턴스 중 하나인 {domain}에 있습니다. 당신의 유저 이름은 {handle} 입니다.",
+  "onboarding.page_one.full_handle": "당신의 풀 핸들",
+  "onboarding.page_one.handle_hint": "이것을 검색하여 친구들이 당신을 찾을 수 있습니다.",
   "onboarding.page_one.welcome": "Mastodon에 어서 오세요!",
   "onboarding.page_six.admin": "이 인스턴스의 관리자는 {admin}입니다.",
   "onboarding.page_six.almost_done": "이상입니다.",
@@ -197,21 +203,30 @@
   "privacy.public.short": "공개",
   "privacy.unlisted.long": "공개 타임라인에 표시하지 않음",
   "privacy.unlisted.short": "타임라인에 비표시",
+  "regeneration_indicator.label": "불러오는 중…",
+  "regeneration_indicator.sublabel": "당신의 홈 피드가 준비되는 중입니다!",
   "relative_time.days": "{number}일 전",
   "relative_time.hours": "{number}시간 전",
   "relative_time.just_now": "방금",
   "relative_time.minutes": "{number}분 전",
   "relative_time.seconds": "{number}초 전",
   "reply_indicator.cancel": "취소",
+  "report.forward": "{target}에 포워드 됨",
+  "report.forward_hint": "이 계정은 다른 서버에 있습니다. 익명화 된 사본을 해당 서버에도 전송할까요?",
+  "report.hint": "신고는 당신의 서버 스태프에게 전송 됩니다. 왜 이 계정을 신고하는 지에 대한 설명을 아래에 작성할 수 있습니다:",
   "report.placeholder": "코멘트",
   "report.submit": "신고하기",
   "report.target": "문제가 된 사용자",
   "search.placeholder": "검색",
   "search_popout.search_format": "고급 검색 방법",
+  "search_popout.tips.full_text": "단순한 텍스트 검색은 당신이 작성했거나, 관심글로 지정했거나, 부스트했거나, 멘션을 받은 게시글, 그리고 유저네임, 디스플레이네임, 해시태그를 반환합니다.",
   "search_popout.tips.hashtag": "해시태그",
   "search_popout.tips.status": "툿",
   "search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다",
   "search_popout.tips.user": "유저",
+  "search_results.accounts": "사람",
+  "search_results.hashtags": "해시태그",
+  "search_results.statuses": "툿",
   "search_results.total": "{count, number}건의 결과",
   "standalone.public_title": "지금 이런 이야기를 하고 있습니다…",
   "status.block": "@{name} 차단",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "이 대화를 뮤트",
   "status.open": "상세 정보 표시",
   "status.pin": "고정",
+  "status.pinned": "고정 된 툿",
   "status.reblog": "부스트",
   "status.reblogged_by": "{name}님이 부스트 했습니다",
   "status.reply": "답장",
@@ -239,7 +255,6 @@
   "status.show_more": "더 보기",
   "status.unmute_conversation": "이 대화의 뮤트 해제하기",
   "status.unpin": "고정 해제",
-  "tabs_bar.compose": "포스트",
   "tabs_bar.federated_timeline": "연합",
   "tabs_bar.home": "홈",
   "tabs_bar.local_timeline": "로컬",
@@ -248,6 +263,7 @@
   "upload_area.title": "드래그 & 드롭으로 업로드",
   "upload_button.label": "미디어 추가",
   "upload_form.description": "시각장애인을 위한 설명",
+  "upload_form.focus": "크롭",
   "upload_form.undo": "재시도",
   "upload_progress.label": "업로드 중...",
   "video.close": "동영상 닫기",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index 6dc7292f1..509382670 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Blokkeer @{name}",
   "account.block_domain": "Negeer alles van {domain}",
+  "account.blocked": "Geblokkeerd",
   "account.disclaimer_full": "De informatie hieronder kan mogelijk een incompleet beeld geven van dit gebruikersprofiel.",
+  "account.domain_blocked": "Domein verborgen",
   "account.edit_profile": "Profiel bewerken",
   "account.follow": "Volgen",
   "account.followers": "Volgers",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} is verhuisd naar:",
   "account.mute": "Negeer @{name}",
   "account.mute_notifications": "Negeer meldingen van @{name}",
+  "account.muted": "Genegeerd",
   "account.posts": "Toots",
+  "account.posts_with_replies": "Toots met reacties",
   "account.report": "Rapporteer @{name}",
   "account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren",
   "account.share": "Profiel van @{name} delen",
@@ -50,7 +54,7 @@
   "column_header.unpin": "Losmaken",
   "column_subheading.navigation": "Navigatie",
   "column_subheading.settings": "Instellingen",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "Deze toot valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare toots kunnen via hashtags gevonden worden.",
   "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en toots zien die je alleen aan volgers hebt gericht.",
   "compose_form.lock_disclaimer.lock": "besloten",
   "compose_form.placeholder": "Wat wil je kwijt?",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Laden…",
   "media_gallery.toggle_visible": "Media wel/niet tonen",
   "missing_indicator.label": "Niet gevonden",
+  "missing_indicator.sublabel": "Deze hulpbron kan niet gevonden worden",
   "mute_modal.hide_notifications": "Verberg meldingen van deze persoon?",
   "navigation_bar.blocks": "Geblokkeerde gebruikers",
   "navigation_bar.community_timeline": "Lokale tijdlijn",
@@ -170,13 +175,14 @@
   "notifications.column_settings.sound": "Geluid afspelen",
   "onboarding.done": "Klaar",
   "onboarding.next": "Volgende",
-  "onboarding.page_five.public_timelines": "De lokale tijdlijn toont openbare toots van iedereen op {domain}. De globale tijdlijn toont openbare toots van iedereen die door gebruikers van {domain} worden gevolgd, dus ook mensen van andere Mastodon-servers. Dit zijn de openbare tijdlijnen en vormen een uitstekende manier om nieuwe mensen te ontdekken.",
+  "onboarding.page_five.public_timelines": "De lokale tijdlijn toont openbare toots van iedereen op {domain}. De globale tijdlijn toont openbare toots van iedereen die door gebruikers van {domain} worden gevolgd, dus ook mensen van andere Mastodonservers. Dit zijn de openbare tijdlijnen en vormen een uitstekende manier om nieuwe mensen te ontdekken.",
   "onboarding.page_four.home": "Deze tijdlijn laat toots zien van mensen die jij volgt.",
-  "onboarding.page_four.notifications": "De kolom met meldingen toont alle interacties die je met andere Mastodon-gebruikers hebt.",
+  "onboarding.page_four.notifications": "De kolom met meldingen toont alle interacties die je met andere Mastodongebruikers hebt.",
   "onboarding.page_one.federation": "Mastodon is een netwerk van onafhankelijke servers die samen een groot sociaal netwerk vormen.",
-  "onboarding.page_one.handle": "Je bevindt je nu op {domain}, dus is jouw volledige Mastodon-adres {handle}",
+  "onboarding.page_one.full_handle": "Jouw volledige Mastodonadres",
+  "onboarding.page_one.handle_hint": "Dit is waarmee jouw vrienden je kunnen vinden.",
   "onboarding.page_one.welcome": "Welkom op Mastodon!",
-  "onboarding.page_six.admin": "De beheerder van jouw Mastodon-server is {admin}.",
+  "onboarding.page_six.admin": "De beheerder van jouw Mastodonserver is {admin}.",
   "onboarding.page_six.almost_done": "Bijna klaar...",
   "onboarding.page_six.appetoot": "Veel succes!",
   "onboarding.page_six.apps_available": "Er zijn {apps} beschikbaar voor iOS, Android en andere platformen.",
@@ -185,7 +191,7 @@
   "onboarding.page_six.read_guidelines": "Vergeet niet de {guidelines} van {domain} te lezen!",
   "onboarding.page_six.various_app": "mobiele apps",
   "onboarding.page_three.profile": "Bewerk jouw profiel om jouw avatar, bio en weergavenaam te veranderen. Daar vind je ook andere instellingen.",
-  "onboarding.page_three.search": "Gebruik de zoekbalk linksboven om andere mensen op Mastodon te vinden en om te zoeken op hashtags, zoals {illustration} en {introductions}. Om iemand te vinden die niet op deze Mastodon-server zit, moet je het volledige Mastodon-adres van deze persoon invoeren.",
+  "onboarding.page_three.search": "Gebruik de zoekbalk linksboven om andere mensen op Mastodon te vinden en om te zoeken op hashtags, zoals {illustration} en {introductions}. Om iemand te vinden die niet op deze Mastodonserver zit, moet je het volledige Mastodonadres van deze persoon invoeren.",
   "onboarding.page_two.compose": "Schrijf berichten (wij noemen dit toots) in het tekstvak in de linkerkolom. Je kan met de pictogrammen daaronder afbeeldingen uploaden, privacy-instellingen veranderen en je tekst een waarschuwing meegeven.",
   "onboarding.skip": "Overslaan",
   "privacy.change": "Zichtbaarheid toot aanpassen",
@@ -197,24 +203,33 @@
   "privacy.public.short": "Openbaar",
   "privacy.unlisted.long": "Niet op openbare tijdlijnen tonen",
   "privacy.unlisted.short": "Minder openbaar",
+  "regeneration_indicator.label": "Aan het laden…",
+  "regeneration_indicator.sublabel": "Jouw tijdlijn wordt aangemaakt!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "nu",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Annuleren",
+  "report.forward": "Doorsturen naar {target}",
+  "report.forward_hint": "Het account bevindt zich op een andere server. Stuur daar eveneens een geanonimiseerde kopie van de gerapporteerde toot(s) naartoe?",
+  "report.hint": "De gerapporteerde toot(s) worden naar de moderatoren van  jouw server gestuurd. Je kunt hieronder een uitleg geven waarom je dit account rapporteert:",
   "report.placeholder": "Extra opmerkingen",
   "report.submit": "Verzenden",
   "report.target": "Rapporteer {target}",
   "search.placeholder": "Zoeken",
   "search_popout.search_format": "Geavanceerd zoeken",
+  "search_popout.tips.full_text": "Gebruik gewone tekst om te zoeken naar toots die jij hebt geschreven, als favoriet markeerde, hebt geboost of waarin jij bent vermeldt, en ook om te zoeken naar gebruikersnamen, weergavenamen en hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "toot",
   "search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags",
   "search_popout.tips.user": "gebruiker",
+  "search_results.accounts": "Gebruikers",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
   "standalone.public_title": "Een kijkje binnenin...",
-  "status.block": "Block @{name}",
+  "status.block": "Blokkeer @{name}",
   "status.cannot_reblog": "Deze toot kan niet geboost worden",
   "status.delete": "Verwijderen",
   "status.embed": "Embed",
@@ -223,10 +238,11 @@
   "status.media_hidden": "Media verborgen",
   "status.mention": "Vermeld @{name}",
   "status.more": "Meer",
-  "status.mute": "Mute @{name}",
+  "status.mute": "Negeer @{name}",
   "status.mute_conversation": "Negeer conversatie",
   "status.open": "Toot volledig tonen",
   "status.pin": "Aan profielpagina vastmaken",
+  "status.pinned": "Vastgemaakte toot",
   "status.reblog": "Boost",
   "status.reblogged_by": "{name} boostte",
   "status.reply": "Reageren",
@@ -239,7 +255,6 @@
   "status.show_more": "Meer tonen",
   "status.unmute_conversation": "Conversatie niet meer negeren",
   "status.unpin": "Van profielpagina losmaken",
-  "tabs_bar.compose": "Schrijven",
   "tabs_bar.federated_timeline": "Globaal",
   "tabs_bar.home": "Start",
   "tabs_bar.local_timeline": "Lokaal",
@@ -248,6 +263,7 @@
   "upload_area.title": "Hierin slepen om te uploaden",
   "upload_button.label": "Media toevoegen",
   "upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",
+  "upload_form.focus": "Bijsnijden",
   "upload_form.undo": "Ongedaan maken",
   "upload_progress.label": "Uploaden...",
   "video.close": "Video sluiten",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 5fbc51ff3..b7ceb9f73 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -1,19 +1,23 @@
 {
   "account.block": "Blokkér @{name}",
   "account.block_domain": "Skjul alt fra {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Informasjonen nedenfor kan gi et ufullstendig bilde av brukerens profil.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Rediger profil",
   "account.follow": "Følg",
   "account.followers": "Følgere",
   "account.follows": "Følger",
   "account.follows_you": "Følger deg",
-  "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.hide_reblogs": "Skjul fremhevinger fra @{name}",
   "account.media": "Media",
   "account.mention": "Nevn @{name}",
   "account.moved_to": "{name} har flyttet til:",
   "account.mute": "Demp @{name}",
   "account.mute_notifications": "Ignorer varsler fra @{name}",
+  "account.muted": "Muted",
   "account.posts": "Innlegg",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Rapportér @{name}",
   "account.requested": "Venter på godkjennelse",
   "account.share": "Del @{name}s profil",
@@ -23,7 +27,7 @@
   "account.unfollow": "Avfølg",
   "account.unmute": "Avdemp @{name}",
   "account.unmute_notifications": "Vis varsler fra @{name}",
-  "account.view_full_profile": "Vis full profil",
+  "account.view_full_profile": "Vis hele profilen",
   "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
   "bundle_column_error.body": "Noe gikk galt mens denne komponenten lastet.",
   "bundle_column_error.retry": "Prøv igjen",
@@ -36,7 +40,7 @@
   "column.favourites": "Likt",
   "column.follow_requests": "Følgeforespørsler",
   "column.home": "Hjem",
-  "column.lists": "Lists",
+  "column.lists": "Lister",
   "column.mutes": "Dempede brukere",
   "column.notifications": "Varsler",
   "column.pins": "Pinned toot",
@@ -50,7 +54,7 @@
   "column_header.unpin": "Løsne",
   "column_subheading.navigation": "Navigasjon",
   "column_subheading.settings": "Innstillinger",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "Denne tuten blir ikke listet under noen emneknagger da den er ulistet. Kun offentlige tuter kan søktes etter med emneknagg.",
   "compose_form.lock_disclaimer": "Din konto er ikke {locked}. Hvem som helst kan følge deg og se dine private poster.",
   "compose_form.lock_disclaimer.lock": "låst",
   "compose_form.placeholder": "Hva har du på hjertet?",
@@ -65,7 +69,7 @@
   "confirmations.delete.confirm": "Slett",
   "confirmations.delete.message": "Er du sikker på at du vil slette denne statusen?",
   "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+  "confirmations.delete_list.message": "Er du sikker på at du vil slette denne listen permanent?",
   "confirmations.domain_block.confirm": "Skjul alt fra domenet",
   "confirmations.domain_block.message": "Er du sikker på at du vil skjule hele domenet {domain}? I de fleste tilfeller er det bedre med målrettet blokkering eller demping.",
   "confirmations.mute.confirm": "Demp",
@@ -75,7 +79,7 @@
   "embed.instructions": "Kopier koden under for å bygge inn denne statusen på hjemmesiden din.",
   "embed.preview": "Slik kommer det til å se ut:",
   "emoji_button.activity": "Aktivitet",
-  "emoji_button.custom": "Custom",
+  "emoji_button.custom": "Tilpasset",
   "emoji_button.flags": "Flagg",
   "emoji_button.food": "Mat og drikke",
   "emoji_button.label": "Sett inn emoji",
@@ -92,7 +96,7 @@
   "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.",
   "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.",
   "empty_column.home.public_timeline": "en offentlig tidslinje",
-  "empty_column.list": "Det er ikke noe i denne listen ennå.",
+  "empty_column.list": "Det er ingenting i denne listen ennå. Når medlemmene av denne listen legger ut nye statuser vil de dukke opp her.",
   "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.",
   "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp",
   "follow_request.authorize": "Autorisér",
@@ -109,48 +113,49 @@
   "home.column_settings.show_replies": "Vis svar",
   "home.settings": "Kolonneinnstillinger",
   "keyboard_shortcuts.back": "for å navigere tilbake",
-  "keyboard_shortcuts.boost": "to boost",
-  "keyboard_shortcuts.column": "to focus a status in one of the columns",
-  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "keyboard_shortcuts.boost": "å fremheve",
+  "keyboard_shortcuts.column": "å fokusere en status i en av kolonnene",
+  "keyboard_shortcuts.compose": "å fokusere komponeringsfeltet",
   "keyboard_shortcuts.description": "Description",
   "keyboard_shortcuts.down": "for å flytte ned i listen",
   "keyboard_shortcuts.enter": "to open status",
   "keyboard_shortcuts.favourite": "for å favorittmarkere",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
   "keyboard_shortcuts.hotkey": "Lyntast",
-  "keyboard_shortcuts.legend": "to display this legend",
-  "keyboard_shortcuts.mention": "to mention author",
+  "keyboard_shortcuts.legend": "å vise denne forklaringen",
+  "keyboard_shortcuts.mention": "å nevne forfatter",
   "keyboard_shortcuts.reply": "for å svare",
-  "keyboard_shortcuts.search": "to focus search",
-  "keyboard_shortcuts.toot": "to start a brand new toot",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "to move up in the list",
+  "keyboard_shortcuts.search": "å fokusere søk",
+  "keyboard_shortcuts.toot": "å starte en helt ny tut",
+  "keyboard_shortcuts.unfocus": "å ufokusere komponerings-/søkefeltet",
+  "keyboard_shortcuts.up": "å flytte opp i listen",
   "lightbox.close": "Lukk",
-  "lightbox.next": "Next",
-  "lightbox.previous": "Previous",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
-  "lists.delete": "Delete list",
-  "lists.edit": "Edit list",
-  "lists.new.create": "Add list",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Your lists",
+  "lightbox.next": "Neste",
+  "lightbox.previous": "Forrige",
+  "lists.account.add": "Legg til i listen",
+  "lists.account.remove": "Fjern fra listen",
+  "lists.delete": "Slett listen",
+  "lists.edit": "Rediger listen",
+  "lists.new.create": "Ligg til liste",
+  "lists.new.title_placeholder": "Ny listetittel",
+  "lists.search": "Søk blant personer du følger",
+  "lists.subheading": "Dine lister",
   "loading_indicator.label": "Laster...",
   "media_gallery.toggle_visible": "Veksle synlighet",
   "missing_indicator.label": "Ikke funnet",
-  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "missing_indicator.sublabel": "Denne ressursen ble ikke funnet",
+  "mute_modal.hide_notifications": "Skjul varslinger fra denne brukeren?",
   "navigation_bar.blocks": "Blokkerte brukere",
   "navigation_bar.community_timeline": "Lokal tidslinje",
   "navigation_bar.edit_profile": "Rediger profil",
-  "navigation_bar.favourites": "Likt",
+  "navigation_bar.favourites": "Favoritter",
   "navigation_bar.follow_requests": "Følgeforespørsler",
   "navigation_bar.info": "Utvidet informasjon",
-  "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
-  "navigation_bar.lists": "Lists",
+  "navigation_bar.keyboard_shortcuts": "Tastatursnarveier",
+  "navigation_bar.lists": "Lister",
   "navigation_bar.logout": "Logg ut",
   "navigation_bar.mutes": "Dempede brukere",
-  "navigation_bar.pins": "Pinned toots",
+  "navigation_bar.pins": "Festa tuter",
   "navigation_bar.preferences": "Preferanser",
   "navigation_bar.public_timeline": "Felles tidslinje",
   "notification.favourite": "{name} likte din status",
@@ -158,13 +163,13 @@
   "notification.mention": "{name} nevnte deg",
   "notification.reblog": "{name} fremhevde din status",
   "notifications.clear": "Fjern varsler",
-  "notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler?",
+  "notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler permanent?",
   "notifications.column_settings.alert": "Skrivebordsvarslinger",
   "notifications.column_settings.favourite": "Likt:",
   "notifications.column_settings.follow": "Nye følgere:",
   "notifications.column_settings.mention": "Nevnt:",
-  "notifications.column_settings.push": "Push notifications",
-  "notifications.column_settings.push_meta": "This device",
+  "notifications.column_settings.push": "Push varsler",
+  "notifications.column_settings.push_meta": "Denne enheten",
   "notifications.column_settings.reblog": "Fremhevet:",
   "notifications.column_settings.show": "Vis i kolonne",
   "notifications.column_settings.sound": "Spill lyd",
@@ -174,14 +179,15 @@
   "onboarding.page_four.home": "Hjem er tidslinjen med alle brukere som du følger.",
   "onboarding.page_four.notifications": "Kolonnen med varsler viser når noen interakterer med deg.",
   "onboarding.page_one.federation": "Mastdodon er et nettverk med uavhengige servere som sammarbeider om å danne et stort sosialt nettverk. Vi kaller disse serverene instanser.",
-  "onboarding.page_one.handle": "Du er på {domain}, så ditt fulle brukernavn er {handle}",
+  "onboarding.page_one.full_handle": "Ditt fulle kallenavn",
+  "onboarding.page_one.handle_hint": "Dette er hva du ber dine venner å søke etter.",
   "onboarding.page_one.welcome": "Velkommen til Mastodon!",
   "onboarding.page_six.admin": "Administratoren på din instans er {admin}.",
   "onboarding.page_six.almost_done": "Snart ferdig...",
   "onboarding.page_six.appetoot": "Bon Appetut!",
   "onboarding.page_six.apps_available": "Det er {apps} tilgjengelig for iOS, Android og andre plattformer.",
   "onboarding.page_six.github": "Mastodon er programvare med fri og åpen kildekode. Du kan rapportere feil, be om hjelp eller foreslå endringer på {github}.",
-  "onboarding.page_six.guidelines": "samfunnets rettningslinjer",
+  "onboarding.page_six.guidelines": "samfunnets retningslinjer",
   "onboarding.page_six.read_guidelines": "Vennligst les {guidelines} for {domain}!",
   "onboarding.page_six.various_app": "mobilapper",
   "onboarding.page_three.profile": "Rediger profilen din for å endre din avatar, biografi, og visningsnavn. Der finner du også andre innstillinger.",
@@ -197,36 +203,46 @@
   "privacy.public.short": "Offentlig",
   "privacy.unlisted.long": "Ikke vis i offentlige tidslinjer",
   "privacy.unlisted.short": "Uoppført",
+  "regeneration_indicator.label": "Laster…",
+  "regeneration_indicator.sublabel": "Dine startside forberedes!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
-  "relative_time.just_now": "now",
+  "relative_time.just_now": "nå",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Avbryt",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Tilleggskommentarer",
   "report.submit": "Send inn",
   "report.target": "Rapporterer",
   "search.placeholder": "Søk",
-  "search_popout.search_format": "Advanced search format",
-  "search_popout.tips.hashtag": "hashtag",
+  "search_popout.search_format": "Avansert søkeformat",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "emneknagg",
   "search_popout.tips.status": "status",
-  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
-  "search_popout.tips.user": "user",
+  "search_popout.tips.text": "Enkel tekst returnerer matchende visningsnavn, brukernavn og emneknagger",
+  "search_popout.tips.user": "bruker",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
-  "standalone.public_title": "A look inside...",
+  "standalone.public_title": "En titt inni...",
   "status.block": "Block @{name}",
   "status.cannot_reblog": "Denne posten kan ikke fremheves",
   "status.delete": "Slett",
-  "status.embed": "Embed",
+  "status.embed": "Bygge inn",
   "status.favourite": "Lik",
   "status.load_more": "Last mer",
   "status.media_hidden": "Media skjult",
   "status.mention": "Nevn @{name}",
-  "status.more": "More",
-  "status.mute": "Mute @{name}",
+  "status.more": "Mer",
+  "status.mute": "Demp @{name}",
   "status.mute_conversation": "Demp samtale",
   "status.open": "Utvid denne statusen",
-  "status.pin": "Pin on profile",
+  "status.pin": "Fest på profilen",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Fremhev",
   "status.reblogged_by": "Fremhevd av {name}",
   "status.reply": "Svar",
@@ -234,24 +250,24 @@
   "status.report": "Rapporter @{name}",
   "status.sensitive_toggle": "Klikk for å vise",
   "status.sensitive_warning": "Følsomt innhold",
-  "status.share": "Share",
+  "status.share": "Del",
   "status.show_less": "Vis mindre",
   "status.show_more": "Vis mer",
   "status.unmute_conversation": "Ikke demp samtale",
-  "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Komponer",
+  "status.unpin": "Angre festing på profilen",
   "tabs_bar.federated_timeline": "Felles",
   "tabs_bar.home": "Hjem",
   "tabs_bar.local_timeline": "Lokal",
   "tabs_bar.notifications": "Varslinger",
-  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "ui.beforeunload": "Din kladd vil bli forkastet om du forlater Mastodon.",
   "upload_area.title": "Dra og slipp for å laste opp",
   "upload_button.label": "Legg til media",
-  "upload_form.description": "Describe for the visually impaired",
+  "upload_form.description": "Beskriv for synshemmede",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Angre",
   "upload_progress.label": "Laster opp...",
-  "video.close": "Close video",
-  "video.exit_fullscreen": "Exit full screen",
+  "video.close": "Lukk video",
+  "video.exit_fullscreen": "Lukk fullskjerm",
   "video.expand": "Utvid video",
   "video.fullscreen": "Full screen",
   "video.hide": "Skjul video",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 6ebd40f5b..c9a15c751 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -1,29 +1,33 @@
 {
   "account.block": "Blocar @{name}",
   "account.block_domain": "Tot amagar del domeni {domain}",
-  "account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incompletas.",
+  "account.blocked": "Blocked",
+  "account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incomplètas.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Modificar lo perfil",
   "account.follow": "Sègre",
   "account.followers": "Seguidors",
   "account.follows": "Abonaments",
   "account.follows_you": "Vos sèc",
-  "account.hide_reblogs": "Rescondre los partages de @{name}",
+  "account.hide_reblogs": "Rescondre los partatges de @{name}",
   "account.media": "Mèdias",
   "account.mention": "Mencionar @{name}",
   "account.moved_to": "{name} a mudat los catons a :",
   "account.mute": "Rescondre @{name}",
   "account.mute_notifications": "Rescondre las notificacions de @{name}",
+  "account.muted": "Muted",
   "account.posts": "Estatuts",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Senhalar @{name}",
   "account.requested": "Invitacion mandada. Clicatz per anullar",
   "account.share": "Partejar lo perfil a @{name}",
-  "account.show_reblogs": "Mostrar los partages de @{name}",
+  "account.show_reblogs": "Mostrar los partatges de @{name}",
   "account.unblock": "Desblocar @{name}",
   "account.unblock_domain": "Desblocar {domain}",
   "account.unfollow": "Quitar de sègre",
   "account.unmute": "Quitar de rescondre @{name}",
   "account.unmute_notifications": "Mostrar las notificacions de @{name}",
-  "account.view_full_profile": "Veire lo perfil complet",
+  "account.view_full_profile": "Veire lo perfil complèt",
   "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven",
   "bundle_column_error.body": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.",
   "bundle_column_error.retry": "Tornar ensajar",
@@ -67,7 +71,7 @@
   "confirmations.delete_list.confirm": "Suprimir",
   "confirmations.delete_list.message": "Sètz segur de voler suprimir aquesta lista per totjorn ?",
   "confirmations.domain_block.confirm": "Amagar tot lo domeni",
-  "confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.",
+  "confirmations.domain_block.message": "Sètz segur segur de voler blocar complètament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.",
   "confirmations.mute.confirm": "Rescondre",
   "confirmations.mute.message": "Sètz segur de voler rescondre {name} ?",
   "confirmations.unfollow.confirm": "Quitar de sègre",
@@ -80,7 +84,7 @@
   "emoji_button.food": "Beure e manjar",
   "emoji_button.label": "Inserir un emoji",
   "emoji_button.nature": "Natura",
-  "emoji_button.not_found": "Cap emoji ! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.not_found": "Cap d’emoji ! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "Objèctes",
   "emoji_button.people": "Gents",
   "emoji_button.recent": "Sovent utilizats",
@@ -108,23 +112,23 @@
   "home.column_settings.show_reblogs": "Mostrar los partatges",
   "home.column_settings.show_replies": "Mostrar las responsas",
   "home.settings": "Paramètres de la colomna",
-  "keyboard_shortcuts.back": "per anar enrèire",
-  "keyboard_shortcuts.boost": "per partejar",
-  "keyboard_shortcuts.column": "per centrar un estatut a una colomna",
-  "keyboard_shortcuts.compose": "per anar al camp tèxte",
+  "keyboard_shortcuts.back": "anar enrèire",
+  "keyboard_shortcuts.boost": "partejar",
+  "keyboard_shortcuts.column": "centrar un estatut a una colomna",
+  "keyboard_shortcuts.compose": "anar al camp tèxte",
   "keyboard_shortcuts.description": "Descripcion",
-  "keyboard_shortcuts.down": "per far davalar dins la lista",
-  "keyboard_shortcuts.enter": "per dobrir los estatuts",
-  "keyboard_shortcuts.favourite": "per apondre als favorits",
+  "keyboard_shortcuts.down": "far davalar dins la lista",
+  "keyboard_shortcuts.enter": "dobrir los estatuts",
+  "keyboard_shortcuts.favourite": "apondre als favorits",
   "keyboard_shortcuts.heading": "Acorchis clavièr",
   "keyboard_shortcuts.hotkey": "Acorchis",
-  "keyboard_shortcuts.legend": "per mostrar aquesta legenda",
-  "keyboard_shortcuts.mention": "per mencionar l’autor",
-  "keyboard_shortcuts.reply": "per respondre",
-  "keyboard_shortcuts.search": "per anar a la recèrca",
-  "keyboard_shortcuts.toot": "per començar un estatut tot novèl",
-  "keyboard_shortcuts.unfocus": "per quitar lo camp tèxte/de recèrca",
-  "keyboard_shortcuts.up": "per far montar dins la lista",
+  "keyboard_shortcuts.legend": "mostrar aquesta legenda",
+  "keyboard_shortcuts.mention": "mencionar l’autor",
+  "keyboard_shortcuts.reply": "respondre",
+  "keyboard_shortcuts.search": "anar a la recèrca",
+  "keyboard_shortcuts.toot": "començar un estatut tot novèl",
+  "keyboard_shortcuts.unfocus": "quitar lo camp tèxte/de recèrca",
+  "keyboard_shortcuts.up": "far montar dins la lista",
   "lightbox.close": "Tampar",
   "lightbox.next": "Seguent",
   "lightbox.previous": "Precedent",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Cargament…",
   "media_gallery.toggle_visible": "Modificar la visibilitat",
   "missing_indicator.label": "Pas trobat",
+  "missing_indicator.sublabel": "Aquesta ressorsa es pas estada trobada",
   "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?",
   "navigation_bar.blocks": "Personas blocadas",
   "navigation_bar.community_timeline": "Flux public local",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "Lo flux d’acuèlh mòstra los estatuts del mond que seguètz.",
   "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos.",
   "onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per construire un malhum mai larg. Òm los apèla instàncias.",
-  "onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}",
+  "onboarding.page_one.full_handle": "Vòstre escais-nom complèt",
+  "onboarding.page_one.handle_hint": "Vos cal dire a vòstres amics de cercar aquò.",
   "onboarding.page_one.welcome": "Benvengut a Mastodon !",
   "onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.",
   "onboarding.page_six.almost_done": "Gaireben acabat…",
@@ -185,7 +191,7 @@
   "onboarding.page_six.read_guidelines": "Mercés de legir la {guidelines} de {domain} !",
   "onboarding.page_six.various_app": "aplicacions per mobil",
   "onboarding.page_three.profile": "Modificatz vòstre perfil per cambiar vòstre avatar, bio e escais-nom. I a enlà totas las preferéncias.",
-  "onboarding.page_three.search": "Emplegatz la barra de recèrca per trobar de mond e engachatz las etiquetas coma {illustration} e {introductions}. Per trobar una persona d’una autra instància, picatz son identificant complet.",
+  "onboarding.page_three.search": "Emplegatz la barra de recèrca per trobar de mond e engachatz las etiquetas coma {illustration} e {introductions}. Per trobar una persona d’una autra instància, picatz son identificant complèt.",
   "onboarding.page_two.compose": "Escrivètz un estatut dempuèi la colomna per compausar. Podètz mandar un imatge, cambiar la confidencialitat e ajustar un avertiment amb las icònas cai-jos.",
   "onboarding.skip": "Passar",
   "privacy.change": "Ajustar la confidencialitat del messatge",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Mostrar pas dins los fluxes publics",
   "privacy.unlisted.short": "Pas-listat",
-  "relative_time.days": "fa {number} d",
-  "relative_time.hours": "fa {number} h",
+  "regeneration_indicator.label": "Cargament…",
+  "regeneration_indicator.sublabel": "Sèm a preparar vòstre flux d’acuèlh !",
+  "relative_time.days": "fa {number}d",
+  "relative_time.hours": "fa {number}h",
   "relative_time.just_now": "ara",
-  "relative_time.minutes": "fa {number} min",
-  "relative_time.seconds": "fa {number} s",
+  "relative_time.minutes": "fa {number}min",
+  "relative_time.seconds": "fa {number}s",
   "reply_indicator.cancel": "Anullar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Comentaris addicionals",
   "report.submit": "Mandar",
   "report.target": "Senhalar {target}",
   "search.placeholder": "Recercar",
   "search_popout.search_format": "Format recèrca avançada",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "etiqueta",
   "search_popout.tips.status": "estatut",
   "search_popout.tips.text": "Lo tèxt brut tòrna escais, noms d’utilizaire e etiquetas correspondents",
   "search_popout.tips.user": "utilizaire",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
   "standalone.public_title": "Una ulhada dedins…",
   "status.block": "Blocar @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Rescondre la conversacion",
   "status.open": "Desplegar aqueste estatut",
   "status.pin": "Penjar al perfil",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Partejar",
   "status.reblogged_by": "{name} a partejat",
   "status.reply": "Respondre",
@@ -239,7 +255,6 @@
   "status.show_more": "Desplegar",
   "status.unmute_conversation": "Tornar mostrar la conversacion",
   "status.unpin": "Tirar del perfil",
-  "tabs_bar.compose": "Compausar",
   "tabs_bar.federated_timeline": "Flux public global",
   "tabs_bar.home": "Acuèlh",
   "tabs_bar.local_timeline": "Flux public local",
@@ -248,12 +263,13 @@
   "upload_area.title": "Lisatz e depausatz per mandar",
   "upload_button.label": "Ajustar un mèdia",
   "upload_form.description": "Descripcion pels mal vesents",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Anullar",
   "upload_progress.label": "Mandadís…",
   "video.close": "Tampar la vidèo",
   "video.exit_fullscreen": "Sortir plen ecran",
   "video.expand": "Agrandir la vidèo",
-  "video.fullscreen": "Ecran complet",
+  "video.fullscreen": "Ecran complèt",
   "video.hide": "Amagar la vidèo",
   "video.mute": "Copar lo son",
   "video.pause": "Pausa",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index d36b1e6ed..391bc6a28 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -1,19 +1,23 @@
 {
   "account.block": "Blokuj @{name}",
   "account.block_domain": "Blokuj wszystko z {domain}",
+  "account.blocked": "Zablokowany",
   "account.disclaimer_full": "Poniższe informacje mogą nie odwzorowywać bezbłędnie profilu użytkownika.",
+  "account.domain_blocked": "Ukryto domenę",
   "account.edit_profile": "Edytuj profil",
   "account.follow": "Śledź",
   "account.followers": "Śledzący",
   "account.follows": "Śledzeni",
   "account.follows_you": "Śledzi Cię",
   "account.hide_reblogs": "Ukryj podbicia od @{name}",
-  "account.media": "Media",
+  "account.media": "Zawartość multimedialna",
   "account.mention": "Wspomnij o @{name}",
   "account.moved_to": "{name} przeniósł się do:",
   "account.mute": "Wycisz @{name}",
   "account.mute_notifications": "Wycisz powiadomienia o @{name}",
+  "account.muted": "Wyciszony",
   "account.posts": "Wpisy",
+  "account.posts_with_replies": "Wpisy z odpowiedziami",
   "account.report": "Zgłoś @{name}",
   "account.requested": "Oczekująca prośba, kliknij aby anulować",
   "account.share": "Udostępnij profil @{name}",
@@ -99,7 +103,7 @@
   "empty_column.home.public_timeline": "publiczna oś czasu",
   "empty_column.list": "Nie ma nic na tej liście. Kiedy członkowie listy dodadzą nowe wpisy, pojawia się one tutaj.",
   "empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.",
-  "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić.",
+  "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić",
   "follow_request.authorize": "Autoryzuj",
   "follow_request.reject": "Odrzuć",
   "getting_started.appsshort": "Aplikacje",
@@ -134,16 +138,17 @@
   "lightbox.next": "Następne",
   "lightbox.previous": "Poprzednie",
   "lists.account.add": "Dodaj do listy",
-  "lists.account.remove": "Remove from list",
+  "lists.account.remove": "Usunąć z listy",
   "lists.delete": "Usuń listę",
   "lists.edit": "Edytuj listę",
   "lists.new.create": "Utwórz listę",
-  "lists.new.title_placeholder": "Wprowadź tytuł listy…",
+  "lists.new.title_placeholder": "Wprowadź tytuł listy",
   "lists.search": "Szukaj wśród osób które śledzisz",
   "lists.subheading": "Twoje listy",
   "loading_indicator.label": "Ładowanie…",
   "media_gallery.toggle_visible": "Przełącz widoczność",
   "missing_indicator.label": "Nie znaleziono",
+  "missing_indicator.sublabel": "Nie można odnaleźć tego zasobu",
   "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?",
   "navigation_bar.blocks": "Zablokowani użytkownicy",
   "navigation_bar.community_timeline": "Lokalna oś czasu",
@@ -181,7 +186,8 @@
   "onboarding.page_four.home": "Główna oś czasu wyświetla publiczne wpisy.",
   "onboarding.page_four.notifications": "Kolumna powiadomień wyświetla, gdy ktoś dokonuje interakcji z tobą.",
   "onboarding.page_one.federation": "Mastodon jest siecią niezależnych serwerów połączonych w jeden portal społecznościowy. Nazywamy te serwery instancjami.",
-  "onboarding.page_one.handle": "Jesteś na domenie {domain}, więc Twój pełny adres to {handle}",
+  "onboarding.page_one.full_handle": "Twój pełny adres",
+  "onboarding.page_one.handle_hint": "Należy go podać znajomym, aby mogli Cię odnaleźć.",
   "onboarding.page_one.welcome": "Witamy w Mastodon!",
   "onboarding.page_six.admin": "Administratorem tej instancji jest {admin}.",
   "onboarding.page_six.almost_done": "Prawie gotowe…",
@@ -204,21 +210,30 @@
   "privacy.public.short": "Publiczny",
   "privacy.unlisted.long": "Niewidoczny na publicznych osiach czasu",
   "privacy.unlisted.short": "Niewidoczny",
+  "regeneration_indicator.label": "Ładuję…",
+  "regeneration_indicator.sublabel": "Twoja oś czasu jest przygotowywana!",
   "relative_time.days": "{number} dni",
   "relative_time.hours": "{number} godz.",
   "relative_time.just_now": "teraz",
   "relative_time.minutes": "{number} min.",
   "relative_time.seconds": "{number} s.",
   "reply_indicator.cancel": "Anuluj",
+  "report.forward": "Przekaż na {target}",
+  "report.forward_hint": "To konto znajduje się na innej instancji. Czy chcesz wysłać anonimową kopię zgłoszenia rnież na nią?",
+  "report.hint": "Zgłoszenie zostanie wysłane moderatorom Twojej instancji. Poniżej możesz też umieścić wyjaśnieni dlaczego zgłaszasz to konto:",
   "report.placeholder": "Dodatkowe komentarze",
   "report.submit": "Wyślij",
   "report.target": "Zgłaszanie {target}",
   "search.placeholder": "Szukaj",
   "search_popout.search_format": "Zaawansowane wyszukiwanie",
+  "search_popout.tips.full_text": "Pozwala na wyszukiwanie wpisów które napisałeś, dodałeś do ulubionych, podbiłeś w których o Tobie wspomniano, oraz pasujące nazwy użytkowników, pełne nazwy i hashtagi.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "wpis",
   "search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów",
   "search_popout.tips.user": "użytkownik",
+  "search_results.accounts": "Ludzie",
+  "search_results.hashtags": "Hashtagi",
+  "search_results.statuses": "Wpisy",
   "search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}",
   "standalone.public_title": "Spojrzenie w głąb…",
   "status.block": "Zablokuj @{name}",
@@ -234,6 +249,7 @@
   "status.mute_conversation": "Wycisz konwersację",
   "status.open": "Rozszerz ten wpis",
   "status.pin": "Przypnij do profilu",
+  "status.pinned": "Przypięty wpis",
   "status.reblog": "Podbij",
   "status.reblogged_by": "{name} podbił",
   "status.reply": "Odpowiedz",
@@ -246,7 +262,6 @@
   "status.show_more": "Pokaż więcej",
   "status.unmute_conversation": "Cofnij wyciszenie konwersacji",
   "status.unpin": "Odepnij z profilu",
-  "tabs_bar.compose": "Napisz",
   "tabs_bar.federated_timeline": "Globalne",
   "tabs_bar.home": "Strona główna",
   "tabs_bar.local_timeline": "Lokalne",
@@ -255,6 +270,7 @@
   "upload_area.title": "Przeciągnij i upuść aby wysłać",
   "upload_button.label": "Dodaj zawartość multimedialną",
   "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących",
+  "upload_form.focus": "Przytnij",
   "upload_form.undo": "Cofnij",
   "upload_progress.label": "Wysyłanie",
   "video.close": "Zamknij film",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 381b80639..6406dbea6 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Bloquear @{name}",
   "account.block_domain": "Esconder tudo de {domain}",
+  "account.blocked": "Bloqueado",
   "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de maneira incompleta.",
+  "account.domain_blocked": "Domínio escondido",
   "account.edit_profile": "Editar perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} se mudou para:",
   "account.mute": "Silenciar @{name}",
   "account.mute_notifications": "Silenciar notificações de @{name}",
-  "account.posts": "Posts",
+  "account.muted": "Silenciado",
+  "account.posts": "Toots",
+  "account.posts_with_replies": "Toots e respostas",
   "account.report": "Denunciar @{name}",
   "account.requested": "Aguardando aprovação. Clique para cancelar a solicitação",
   "account.share": "Compartilhar perfil de @{name}",
@@ -90,7 +94,7 @@
   "emoji_button.travel": "Viagens & Lugares",
   "empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
   "empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag.",
-  "empty_column.home": "Você ainda não segue usuário algo. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.",
+  "empty_column.home": "Você ainda não segue usuário algum. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.",
   "empty_column.home.public_timeline": "global",
   "empty_column.list": "Ainda não há nada nesta lista. Quando membros dessa lista fizerem novas postagens, elas aparecerão aqui.",
   "empty_column.notifications": "Você ainda não possui notificações. Interaja com outros usuários para começar a conversar.",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Carregando...",
   "media_gallery.toggle_visible": "Esconder/Mostrar",
   "missing_indicator.label": "Não encontrado",
+  "missing_indicator.sublabel": "Esse recurso não pôde ser encontrado",
   "mute_modal.hide_notifications": "Esconder notificações deste usuário?",
   "navigation_bar.blocks": "Usuários bloqueados",
   "navigation_bar.community_timeline": "Local",
@@ -173,8 +178,9 @@
   "onboarding.page_five.public_timelines": "A timeline local mostra postagens públicas de todos os usuários no {domain}. A timeline federada mostra todas as postagens de todas as pessoas que pessoas no {domain} seguem. Estas são as timelines públicas, uma ótima maneira de conhecer novas pessoas.",
   "onboarding.page_four.home": "A página inicial mostra postagens de pessoas que você segue.",
   "onboarding.page_four.notifications": "A coluna de notificações te mostra quando alguém interage com você.",
-  "onboarding.page_one.federation": "Mastodon é uma rede d servidores independentes se juntando para fazer uma grande rede social. Nós chamamos estes servidores de instâncias.",
-  "onboarding.page_one.handle": "Você está no {domain}, então o seu nome de usuário completo é {handle}",
+  "onboarding.page_one.federation": "Mastodon é uma rede de servidores independentes que se juntam para fazer uma grande rede social. Nós chamamos estes servidores de instâncias.",
+  "onboarding.page_one.full_handle": "Seu nome de usuário completo",
+  "onboarding.page_one.handle_hint": "Isso é o que você diz aos seus amigos para que eles possam te mandar mensagens ou te seguir a partir de outra instância.",
   "onboarding.page_one.welcome": "Seja bem-vindo(a) ao Mastodon!",
   "onboarding.page_six.admin": "O administrador de sua instância é {admin}.",
   "onboarding.page_six.almost_done": "Quase acabando...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Pública",
   "privacy.unlisted.long": "Não publicar em feeds públicos",
   "privacy.unlisted.short": "Não listada",
+  "regeneration_indicator.label": "Carregando…",
+  "regeneration_indicator.sublabel": "Sua página inicial está sendo preparada!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "agora",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancelar",
+  "report.forward": "Encaminhar para {target}",
+  "report.forward_hint": "Essa conta pertence à um outro servidor. Encaminhar uma cópia da denúncia com seus dados tornados anônimos para esse servidor?",
+  "report.hint": "A sua denúncia será enviada aos moderadores da instância. Você pode adicionar uma explicação de porque você está denunciando essa conta abaixo:",
   "report.placeholder": "Comentários adicionais",
   "report.submit": "Enviar",
   "report.target": "Denunciar",
   "search.placeholder": "Pesquisar",
   "search_popout.search_format": "Formato de busca avançado",
+  "search_popout.tips.full_text": "Texto simples retorna status que você escreveu, favoritou, compartilhou ou em que tenha sido mencionado; também retorna nomes de exibição, usuários e hashtags correspondentes.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Texto simples retorna nomes de exibição, usuários e hashtags correspondentes",
   "search_popout.tips.user": "usuário",
+  "search_results.accounts": "Pessoas",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Dê uma espiada...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Silenciar conversa",
   "status.open": "Expandir",
   "status.pin": "Fixar no perfil",
+  "status.pinned": "Toot fixado",
   "status.reblog": "Compartilhar",
   "status.reblogged_by": "{name} compartilhou",
   "status.reply": "Responder",
@@ -239,7 +255,6 @@
   "status.show_more": "Mostrar mais",
   "status.unmute_conversation": "Desativar silêncio desta conversa",
   "status.unpin": "Desafixar do perfil",
-  "tabs_bar.compose": "Criar",
   "tabs_bar.federated_timeline": "Global",
   "tabs_bar.home": "Página inicial",
   "tabs_bar.local_timeline": "Local",
@@ -248,6 +263,7 @@
   "upload_area.title": "Arraste e solte para enviar",
   "upload_button.label": "Adicionar mídia",
   "upload_form.description": "Descreva a imagem para deficientes visuais",
+  "upload_form.focus": "Recortar",
   "upload_form.undo": "Desfazer",
   "upload_progress.label": "Salvando...",
   "video.close": "Fechar vídeo",
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json
index f566f551b..f059e7c20 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Bloquear @{name}",
   "account.block_domain": "Esconder tudo do domínio {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Editar perfil",
   "account.follow": "Seguir",
   "account.followers": "Seguidores",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} mudou a sua conta para:",
   "account.mute": "Silenciar @{name}",
   "account.mute_notifications": "Silenciar notificações de @{name}",
+  "account.muted": "Muted",
   "account.posts": "Posts",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Denunciar @{name}",
   "account.requested": "A aguardar aprovação",
   "account.share": "Partilhar o perfil @{name}",
@@ -50,7 +54,7 @@
   "column_header.unpin": "Desafixar",
   "column_subheading.navigation": "Navegação",
   "column_subheading.settings": "Preferências",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "Esta pulbicacção não será listada em nenhuma hashtag por ser não listada. Somente publicações públicas podem ser pesquisadas por hashtag.",
   "compose_form.lock_disclaimer": "A tua conta não está {locked}. Qualquer pessoa pode seguir-te e ver as publicações direcionadas apenas a seguidores.",
   "compose_form.lock_disclaimer.lock": "bloqueada",
   "compose_form.placeholder": "Em que estás a pensar?",
@@ -108,7 +112,7 @@
   "home.column_settings.show_reblogs": "Mostrar as partilhas",
   "home.column_settings.show_replies": "Mostrar as respostas",
   "home.settings": "Parâmetros da listagem",
-  "keyboard_shortcuts.back": "para navegar de volta",
+  "keyboard_shortcuts.back": "para voltar",
   "keyboard_shortcuts.boost": "para partilhar",
   "keyboard_shortcuts.column": "para focar uma publicação numa das colunas",
   "keyboard_shortcuts.compose": "para focar na área de publicação",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "A carregar...",
   "media_gallery.toggle_visible": "Esconder/Mostrar",
   "missing_indicator.label": "Não encontrado",
+  "missing_indicator.sublabel": "Este recurso não foi encontrado",
   "mute_modal.hide_notifications": "Esconder notificações deste utilizador?",
   "navigation_bar.blocks": "Utilizadores bloqueados",
   "navigation_bar.community_timeline": "Local",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "A timeline home mostra as publicações de pessoas que tu segues.",
   "onboarding.page_four.notifications": "A coluna de notificações mostra-te quando alguém interage contigo.",
   "onboarding.page_one.federation": "Mastodon é uma rede de servidores independentes ligados entre si para fazer uma grande rede social. Nós chamamos instâncias a estes servidores.",
-  "onboarding.page_one.handle": "Tu estás em {domain}, por isso o teu endereço completo de utilizador é {handle}",
+  "onboarding.page_one.full_handle": "O teu nome de utilizador completo",
+  "onboarding.page_one.handle_hint": "Isto é o que dizes aos teus amigos para pesquisar.",
   "onboarding.page_one.welcome": "Bem-vindo(a) ao Mastodon!",
   "onboarding.page_six.admin": "O administrador da tua instância é {admin}.",
   "onboarding.page_six.almost_done": "Quase pronto...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Público",
   "privacy.unlisted.long": "Não publicar nos feeds públicos",
   "privacy.unlisted.short": "Não listar",
+  "regeneration_indicator.label": "A carregar…",
+  "regeneration_indicator.sublabel": "A tua home está a ser preparada!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "agora",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancelar",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Comentários adicionais",
   "report.submit": "Enviar",
   "report.target": "Denunciar",
   "search.placeholder": "Pesquisar",
   "search_popout.search_format": "Formato avançado de pesquisa",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags",
   "search_popout.tips.user": "utilizador",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Espreitar lá dentro...",
   "status.block": "Block @{name}",
@@ -223,10 +238,11 @@
   "status.media_hidden": "Media escondida",
   "status.mention": "Mencionar @{name}",
   "status.more": "Mais",
-  "status.mute": "Mute @{name}",
+  "status.mute": "Silenciar @{name}",
   "status.mute_conversation": "Silenciar conversa",
   "status.open": "Expandir",
   "status.pin": "Fixar no perfil",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Partilhar",
   "status.reblogged_by": "{name} partilhou",
   "status.reply": "Responder",
@@ -239,7 +255,6 @@
   "status.show_more": "Mostrar mais",
   "status.unmute_conversation": "Deixar de silenciar esta conversa",
   "status.unpin": "Não fixar no perfil",
-  "tabs_bar.compose": "Criar",
   "tabs_bar.federated_timeline": "Global",
   "tabs_bar.home": "Home",
   "tabs_bar.local_timeline": "Local",
@@ -248,6 +263,7 @@
   "upload_area.title": "Arraste e solte para enviar",
   "upload_button.label": "Adicionar media",
   "upload_form.description": "Descrição da imagem para pessoas com dificuldades visuais",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Anular",
   "upload_progress.label": "A gravar...",
   "video.close": "Fechar vídeo",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 0fec70df0..06a7d732b 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -1,28 +1,32 @@
 {
   "account.block": "Блокировать",
   "account.block_domain": "Блокировать все с {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Нижеуказанная информация может не полностью отражать профиль пользователя.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Изменить профиль",
   "account.follow": "Подписаться",
   "account.followers": "Подписаны",
   "account.follows": "Подписки",
   "account.follows_you": "Подписан(а) на Вас",
-  "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.hide_reblogs": "Скрыть продвижения от @{name}",
   "account.media": "Медиаконтент",
   "account.mention": "Упомянуть",
-  "account.moved_to": "{name} has moved to:",
+  "account.moved_to": "Ищите {name} здесь:",
   "account.mute": "Заглушить",
-  "account.mute_notifications": "Mute notifications from @{name}",
+  "account.mute_notifications": "Скрыть уведомления от @{name}",
+  "account.muted": "Muted",
   "account.posts": "Посты",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Пожаловаться",
   "account.requested": "Ожидает подтверждения",
   "account.share": "Поделиться профилем @{name}",
-  "account.show_reblogs": "Show boosts from @{name}",
+  "account.show_reblogs": "Показывать продвижения от @{name}",
   "account.unblock": "Разблокировать",
   "account.unblock_domain": "Разблокировать {domain}",
   "account.unfollow": "Отписаться",
   "account.unmute": "Снять глушение",
-  "account.unmute_notifications": "Unmute notifications from @{name}",
+  "account.unmute_notifications": "Показывать уведомления от @{name}",
   "account.view_full_profile": "Показать полный профиль",
   "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
   "bundle_column_error.body": "Что-то пошло не так при загрузке этого компонента.",
@@ -36,10 +40,10 @@
   "column.favourites": "Понравившееся",
   "column.follow_requests": "Запросы на подписку",
   "column.home": "Главная",
-  "column.lists": "Lists",
+  "column.lists": "Списки",
   "column.mutes": "Список глушения",
   "column.notifications": "Уведомления",
-  "column.pins": "Pinned toot",
+  "column.pins": "Закреплённый пост",
   "column.public": "Глобальная лента",
   "column_back_button.label": "Назад",
   "column_header.hide_settings": "Скрыть настройки",
@@ -50,7 +54,7 @@
   "column_header.unpin": "Открепить",
   "column_subheading.navigation": "Навигация",
   "column_subheading.settings": "Настройки",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "Этот пост не будет показывается в поиске по хэштегу, т.к. он непубличный. Только публичные посты можно найти в поиске по хэштегу.",
   "compose_form.lock_disclaimer": "Ваш аккаунт не {locked}. Любой человек может подписаться на Вас и просматривать посты для подписчиков.",
   "compose_form.lock_disclaimer.lock": "закрыт",
   "compose_form.placeholder": "О чем Вы думаете?",
@@ -64,8 +68,8 @@
   "confirmations.block.message": "Вы уверены, что хотите заблокировать {name}?",
   "confirmations.delete.confirm": "Удалить",
   "confirmations.delete.message": "Вы уверены, что хотите удалить этот статус?",
-  "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+  "confirmations.delete_list.confirm": "Удалить",
+  "confirmations.delete_list.message": "Вы действительно хотите навсегда удалить этот список?",
   "confirmations.domain_block.confirm": "Блокировать весь домен",
   "confirmations.domain_block.message": "Вы на самом деле уверены, что хотите блокировать весь {domain}? В большинстве случаев нескольких отдельных блокировок или глушений достаточно.",
   "confirmations.mute.confirm": "Заглушить",
@@ -92,7 +96,7 @@
   "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.",
   "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
   "empty_column.home.public_timeline": "публичные ленты",
-  "empty_column.list": "There is nothing in this list yet.",
+  "empty_column.list": "В этом списке пока ничего нет.",
   "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.",
   "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
   "follow_request.authorize": "Авторизовать",
@@ -108,49 +112,50 @@
   "home.column_settings.show_reblogs": "Показывать продвижения",
   "home.column_settings.show_replies": "Показывать ответы",
   "home.settings": "Настройки колонки",
-  "keyboard_shortcuts.back": "to navigate back",
-  "keyboard_shortcuts.boost": "to boost",
-  "keyboard_shortcuts.column": "to focus a status in one of the columns",
-  "keyboard_shortcuts.compose": "to focus the compose textarea",
-  "keyboard_shortcuts.description": "Description",
-  "keyboard_shortcuts.down": "to move down in the list",
-  "keyboard_shortcuts.enter": "to open status",
-  "keyboard_shortcuts.favourite": "to favourite",
-  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.hotkey": "Hotkey",
-  "keyboard_shortcuts.legend": "to display this legend",
-  "keyboard_shortcuts.mention": "to mention author",
-  "keyboard_shortcuts.reply": "to reply",
-  "keyboard_shortcuts.search": "to focus search",
-  "keyboard_shortcuts.toot": "to start a brand new toot",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "to move up in the list",
+  "keyboard_shortcuts.back": "перейти назад",
+  "keyboard_shortcuts.boost": "продвинуть пост",
+  "keyboard_shortcuts.column": "фокус на одном из столбцов",
+  "keyboard_shortcuts.compose": "фокус на поле ввода",
+  "keyboard_shortcuts.description": "Описание",
+  "keyboard_shortcuts.down": "вниз по списку",
+  "keyboard_shortcuts.enter": "развернуть пост",
+  "keyboard_shortcuts.favourite": "в избранное",
+  "keyboard_shortcuts.heading": "Сочетания клавиш",
+  "keyboard_shortcuts.hotkey": "Гор. клавиша",
+  "keyboard_shortcuts.legend": "показать это окно",
+  "keyboard_shortcuts.mention": "упомянуть автора поста",
+  "keyboard_shortcuts.reply": "ответить",
+  "keyboard_shortcuts.search": "перейти к поиску",
+  "keyboard_shortcuts.toot": "начать писать новый пост",
+  "keyboard_shortcuts.unfocus": "убрать фокус с поля ввода/поиска",
+  "keyboard_shortcuts.up": "вверх по списку",
   "lightbox.close": "Закрыть",
   "lightbox.next": "Далее",
   "lightbox.previous": "Назад",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
-  "lists.delete": "Delete list",
-  "lists.edit": "Edit list",
-  "lists.new.create": "Add list",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Your lists",
+  "lists.account.add": "Добавить в список",
+  "lists.account.remove": "Убрать из списка",
+  "lists.delete": "Удалить список",
+  "lists.edit": "Изменить список",
+  "lists.new.create": "Новый список",
+  "lists.new.title_placeholder": "Заголовок списка",
+  "lists.search": "Искать из ваших подписок",
+  "lists.subheading": "Ваши списки",
   "loading_indicator.label": "Загрузка...",
   "media_gallery.toggle_visible": "Показать/скрыть",
   "missing_indicator.label": "Не найдено",
-  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "missing_indicator.sublabel": "Запрашиваемый ресурс не найден",
+  "mute_modal.hide_notifications": "Убрать уведомления от этого пользователя?",
   "navigation_bar.blocks": "Список блокировки",
   "navigation_bar.community_timeline": "Локальная лента",
   "navigation_bar.edit_profile": "Изменить профиль",
   "navigation_bar.favourites": "Понравившееся",
   "navigation_bar.follow_requests": "Запросы на подписку",
   "navigation_bar.info": "Об узле",
-  "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
-  "navigation_bar.lists": "Lists",
+  "navigation_bar.keyboard_shortcuts": "Сочетания клавиш",
+  "navigation_bar.lists": "Списки",
   "navigation_bar.logout": "Выйти",
   "navigation_bar.mutes": "Список глушения",
-  "navigation_bar.pins": "Pinned toots",
+  "navigation_bar.pins": "Закреплённые посты",
   "navigation_bar.preferences": "Опции",
   "navigation_bar.public_timeline": "Глобальная лента",
   "notification.favourite": "{name} понравился Ваш статус",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "Домашняя лента показывает посты от тех, на кого Вы подписаны.",
   "onboarding.page_four.notifications": "Колонка уведомлений сообщает о взаимодействии с Вами других людей.",
   "onboarding.page_one.federation": "Mastodon - это сеть независимых серверов, которые вместе образуют единую социальную сеть. Мы называем эти сервера узлами.",
-  "onboarding.page_one.handle": "Вы находитесь на {domain}, поэтому Ваше полное имя пользователя - {handle}",
+  "onboarding.page_one.full_handle": "Всё в ваших руках",
+  "onboarding.page_one.handle_hint": "Это то, что вы посоветуете искать своим друзьям.",
   "onboarding.page_one.welcome": "Добро пожаловать в Mastodon!",
   "onboarding.page_six.admin": "Админ Вашего узла - {admin}.",
   "onboarding.page_six.almost_done": "Почти готово...",
@@ -197,24 +203,33 @@
   "privacy.public.short": "Публичный",
   "privacy.unlisted.long": "Не показывать в лентах",
   "privacy.unlisted.short": "Скрытый",
+  "regeneration_indicator.label": "Загрузка…",
+  "regeneration_indicator.sublabel": "Ваша домашняя лента готовится!",
   "relative_time.days": "{number}д",
   "relative_time.hours": "{number}ч",
   "relative_time.just_now": "только что",
   "relative_time.minutes": "{number}м",
   "relative_time.seconds": "{number}с",
   "reply_indicator.cancel": "Отмена",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Комментарий",
   "report.submit": "Отправить",
   "report.target": "Жалуемся на",
   "search.placeholder": "Поиск",
   "search_popout.search_format": "Продвинутый формат поиска",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "хэштег",
   "search_popout.tips.status": "статус",
   "search_popout.tips.text": "Простой ввод текста покажет совпадающие имена пользователей, отображаемые имена и хэштеги",
   "search_popout.tips.user": "пользователь",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}",
   "standalone.public_title": "Прямо сейчас",
-  "status.block": "Block @{name}",
+  "status.block": "Заблокировать @{name}",
   "status.cannot_reblog": "Этот статус не может быть продвинут",
   "status.delete": "Удалить",
   "status.embed": "Встроить",
@@ -223,10 +238,11 @@
   "status.media_hidden": "Медиаконтент скрыт",
   "status.mention": "Упомянуть @{name}",
   "status.more": "Больше",
-  "status.mute": "Mute @{name}",
+  "status.mute": "Заглушить @{name}",
   "status.mute_conversation": "Заглушить тред",
   "status.open": "Развернуть статус",
   "status.pin": "Закрепить в профиле",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Продвинуть",
   "status.reblogged_by": "{name} продвинул(а)",
   "status.reply": "Ответить",
@@ -239,15 +255,15 @@
   "status.show_more": "Развернуть",
   "status.unmute_conversation": "Снять глушение с треда",
   "status.unpin": "Открепить от профиля",
-  "tabs_bar.compose": "Написать",
   "tabs_bar.federated_timeline": "Глобальная",
   "tabs_bar.home": "Главная",
   "tabs_bar.local_timeline": "Локальная",
   "tabs_bar.notifications": "Уведомления",
-  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.",
   "upload_area.title": "Перетащите сюда, чтобы загрузить",
   "upload_button.label": "Добавить медиаконтент",
   "upload_form.description": "Описать для людей с нарушениями зрения",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Отменить",
   "upload_progress.label": "Загрузка...",
   "video.close": "Закрыть видео",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index c38bfd3c6..bb86165ad 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -1,32 +1,36 @@
 {
   "account.block": "Blokovať @{name}",
-  "account.block_domain": "Blokovať všetko z {domain}",
-  "account.disclaimer_full": "Inofrmácie nižšie nemusia reflektovať použivateľský účet kompletne.",
+  "account.block_domain": "Ukryť všetko z {domain}",
+  "account.blocked": "Blokovaný/á",
+  "account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.",
+  "account.domain_blocked": "Doména ukrytá",
   "account.edit_profile": "Upraviť profil",
-  "account.follow": "Sledovať",
+  "account.follow": "Následovať",
   "account.followers": "Sledujúci",
-  "account.follows": "Sledovaní",
-  "account.follows_you": "Sleduje teba",
-  "account.hide_reblogs": "Hide boosts from @{name}",
-  "account.media": "Média",
-  "account.mention": "Napísať @{name}",
-  "account.moved_to": "{name} has moved to:",
+  "account.follows": "Sledujete",
+  "account.follows_you": "Následuje ťa",
+  "account.hide_reblogs": "Skryť povýšenia od @{name}",
+  "account.media": "Médiá",
+  "account.mention": "Spomeňte @{name}",
+  "account.moved_to": "{name} sa presunul/a na:",
   "account.mute": "Ignorovať @{name}",
-  "account.mute_notifications": "Mute notifications from @{name}",
-  "account.posts": "Správ",
+  "account.mute_notifications": "Stĺmiť notifikácie od @{name}",
+  "account.muted": "Utíšený/á",
+  "account.posts": "Hlášky",
+  "account.posts_with_replies": "Príspevky s odpoveďami",
   "account.report": "Nahlásiť @{name}",
-  "account.requested": "Čaká na schválenie. Klikni na zrušenie žiadosti",
+  "account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti",
   "account.share": "Zdieľať @{name} profil",
-  "account.show_reblogs": "Show boosts from @{name}",
+  "account.show_reblogs": "Zobraziť povýšenia od @{name}",
   "account.unblock": "Odblokovať @{name}",
   "account.unblock_domain": "Prestať blokovať {domain}",
   "account.unfollow": "Prestať nasledovať",
   "account.unmute": "Prestať ignorovať @{name}",
-  "account.unmute_notifications": "Unmute notifications from @{name}",
+  "account.unmute_notifications": "Odtĺmiť notifikácie od @{name}",
   "account.view_full_profile": "Pozri celý profil",
-  "boost_modal.combo": "Nabudúce môžeš kliknúť {combo} a preskočiť",
+  "boost_modal.combo": "Nabudúce môžete kliknúť {combo} aby ste preskočili",
   "bundle_column_error.body": "Nastala chyba pri načítaní tohto komponentu.",
-  "bundle_column_error.retry": "Skús znova",
+  "bundle_column_error.retry": "Skúste znova",
   "bundle_column_error.title": "Chyba siete",
   "bundle_modal_error.close": "Zatvoriť",
   "bundle_modal_error.message": "Nastala chyba pri načítaní tohto komponentu.",
@@ -34,9 +38,9 @@
   "column.blocks": "Blokovaní používatelia",
   "column.community": "Lokálna časová os",
   "column.favourites": "Obľúbené",
-  "column.follow_requests": "Žiadosti",
-  "column.home": "Moja časová os",
-  "column.lists": "Lists",
+  "column.follow_requests": "Žiadosti o sledovaní",
+  "column.home": "Domov",
+  "column.lists": "Zoznamy",
   "column.mutes": "Ignorovaní používatelia",
   "column.notifications": "Notifikácie",
   "column.pins": "Pripnuté toots",
@@ -50,210 +54,222 @@
   "column_header.unpin": "Odopnúť",
   "column_subheading.navigation": "Navigácia",
   "column_subheading.settings": "Nastavenia",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
-  "compose_form.lock_disclaimer": "Tvoj účet nie je zamknutý. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.",
+  "compose_form.hashtag_warning": "Tento toot nebude zobrazený pod žiadným haštagom lebo nieje listovaný. Iba verejné toots môžu byť nájdené podľa haštagu.",
+  "compose_form.lock_disclaimer": "Váš účet nie je zamknutý. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.",
   "compose_form.lock_disclaimer.lock": "zamknutý",
-  "compose_form.placeholder": "Čo máš na mysli?",
+  "compose_form.placeholder": "Na čo myslíš?",
   "compose_form.publish": "Toot",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive": "Označ súbor ako chúlostivý",
+  "compose_form.sensitive": "Označ médiá ako chúlostivé",
   "compose_form.spoiler": "Skryť text za varovanie",
-  "compose_form.spoiler_placeholder": "Napíš sem tvoje varovanie",
+  "compose_form.spoiler_placeholder": "Sem napíšte vaše varovanie",
   "confirmation_modal.cancel": "Zrušiť",
   "confirmations.block.confirm": "Blokovať",
-  "confirmations.block.message": "Naozaj chceš blokovať {name}?",
+  "confirmations.block.message": "Ste si istý, že chcete blokovať {name}?",
   "confirmations.delete.confirm": "Zmazať",
-  "confirmations.delete.message": "Naozaj chceš zmazať túto správu?",
-  "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+  "confirmations.delete.message": "Naozaj chcete vymazať túto správu?",
+  "confirmations.delete_list.confirm": "Vymazať",
+  "confirmations.delete_list.message": "Ste si istý/á, že chceťe navždy vymazať tento zoznam?",
   "confirmations.domain_block.confirm": "Skryť celú doménu",
-  "confirmations.domain_block.message": "Si si naozaj istý, že chceš blokovať celú {domain}? Vo väčšine prípadov stačí blokovať alebo ignorovať daných používateľov.",
+  "confirmations.domain_block.message": "Ste si naozaj istý, že chcete blokovať celú {domain}? Vo väčšine prípadov stačí blokovať alebo ignorovať daných používateľov, čiže to sa doporučuje.",
   "confirmations.mute.confirm": "Ignoruj",
-  "confirmations.mute.message": "Naozaj chceš ignorovať {name}?",
+  "confirmations.mute.message": "Naozaj chcete ignorovať {name}?",
   "confirmations.unfollow.confirm": "Nesledovať",
-  "confirmations.unfollow.message": "Naozaj chceš prestať sledovať {name}?",
-  "embed.instructions": "Skopíruj kód nižšie a ridaj tento status na tvoju web stránku.",
-  "embed.preview": "Tu je ukážka ako to bude vyzerať:",
-  "emoji_button.activity": "Aktivity",
+  "confirmations.unfollow.message": "Naozaj chcete prestať sledovať {name}?",
+  "embed.instructions": "Umiestnite kód uvedený nižšie pre pridanie tohto statusu na vašu web stránku.",
+  "embed.preview": "Tu je ako to bude vyzerať:",
+  "emoji_button.activity": "Aktivita",
   "emoji_button.custom": "Vlastné",
   "emoji_button.flags": "Vlajky",
   "emoji_button.food": "Jedlá a nápoje",
-  "emoji_button.label": "Vlož emoji",
-  "emoji_button.nature": "Zvieratká",
-  "emoji_button.not_found": "Nenájdené",
+  "emoji_button.label": "Vložiť emotikony",
+  "emoji_button.nature": "Prírodné",
+  "emoji_button.not_found": "Nie emotikony!! (╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "Predmety",
   "emoji_button.people": "Ľudia",
   "emoji_button.recent": "Často používané",
-  "emoji_button.search": "Hľadaj...",
+  "emoji_button.search": "Hľadať...",
   "emoji_button.search_results": "Nájdené",
   "emoji_button.symbols": "Symboly",
   "emoji_button.travel": "Cestovanie a miesta",
-  "empty_column.community": "Lokálna časová os je prázdna. Napíš niečo aby sa to začalo hýbať!",
-  "empty_column.hashtag": "Ešte nič nie je v tomto hashtag-u.",
-  "empty_column.home": "Ešte nesleduješ nikoho. Pre začiatok pozri {public} alebo použi vyhľadávanie aby si našiel ostatných používateľov.",
-  "empty_column.home.public_timeline": "verejnú časovú os",
-  "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
-  "empty_column.notifications": "Nemáš žiadne notifikácie. Napíš niekomu, nasleduj niekoho alebo komunikuj s ostatnými.",
-  "empty_column.public": "Ešte tu nič nie je. Napíš niečo verejne alebo začni sledovať používateľov z iných Mastodon serverov aby tu niečo bolo",
-  "follow_request.authorize": "Potvrdiť",
+  "empty_column.community": "Lokálna časová os je prázdna. Napíšte niečo, aby sa to tu začalo hýbať!",
+  "empty_column.hashtag": "Pod týmto hashtagom sa ešte nič nenachádza.",
+  "empty_column.home": "Vaša lokálna osa je zatiaľ prázdna! Pre začiatok pozrite {public} alebo použite vyhľadávanie a nájdite tak ostatných používateľov.",
+  "empty_column.home.public_timeline": "verejná časová os",
+  "empty_column.list": "Tento zoznam je ešte prázdny. Keď ale členovia tohoto zoznamu napíšu nové správy, tak tie sa objavia priamo tu.",
+  "empty_column.notifications": "Nemáte ešte žiadne notifikácie. Napíšte niekomu, následujte niekoho a komunikujte s ostatnými aby diskusia mohla začať.",
+  "empty_column.public": "Ešte tu nič nie je. Napíšte niečo verejne alebo začnite sledovať používateľov z iných Mastodon serverov aby tu niečo pribudlo",
+  "follow_request.authorize": "Povoliť prístup",
   "follow_request.reject": "Odmietnúť",
   "getting_started.appsshort": "Aplikácie",
   "getting_started.faq": "FAQ",
   "getting_started.heading": "Začíname",
-  "getting_started.open_source_notice": "Mastodon má otvorený kód. Reportovať chyby alebo prispievať vlastným kódom môžeš na GitHube v {github}.",
+  "getting_started.open_source_notice": "Mastodon má otvorený kód. Nahlásiť chyby, alebo prispievať vlastným kódom môžete na GitHube v {github}.",
   "getting_started.userguide": "Používateľská príručka",
   "home.column_settings.advanced": "Rozšírené",
   "home.column_settings.basic": "Základné",
   "home.column_settings.filter_regex": "Filtrovať použitím regulárnych výrazov",
-  "home.column_settings.show_reblogs": "Zobraziť boosts",
+  "home.column_settings.show_reblogs": "Zobraziť povýšené",
   "home.column_settings.show_replies": "Zobraziť odpovede",
   "home.settings": "Nastavenia stĺpcov",
-  "keyboard_shortcuts.back": "to navigate back",
-  "keyboard_shortcuts.boost": "to boost",
-  "keyboard_shortcuts.column": "to focus a status in one of the columns",
-  "keyboard_shortcuts.compose": "to focus the compose textarea",
-  "keyboard_shortcuts.description": "Description",
-  "keyboard_shortcuts.down": "to move down in the list",
-  "keyboard_shortcuts.enter": "to open status",
-  "keyboard_shortcuts.favourite": "to favourite",
-  "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.hotkey": "Hotkey",
-  "keyboard_shortcuts.legend": "to display this legend",
-  "keyboard_shortcuts.mention": "to mention author",
-  "keyboard_shortcuts.reply": "to reply",
-  "keyboard_shortcuts.search": "to focus search",
-  "keyboard_shortcuts.toot": "to start a brand new toot",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "to move up in the list",
-  "lightbox.close": "Zavrieť",
-  "lightbox.next": "Ďalší",
+  "keyboard_shortcuts.back": "dostať sa naspäť",
+  "keyboard_shortcuts.boost": "vyzdvihnúť",
+  "keyboard_shortcuts.column": "zamerať sa na status v jednom zo stĺpcov",
+  "keyboard_shortcuts.compose": "zamerať sa na písaciu plochu",
+  "keyboard_shortcuts.description": "Popis",
+  "keyboard_shortcuts.down": "posunúť sa dole v zozname",
+  "keyboard_shortcuts.enter": "otvoriť správu",
+  "keyboard_shortcuts.favourite": "pridať do obľúbených",
+  "keyboard_shortcuts.heading": "Klávesové skratky",
+  "keyboard_shortcuts.hotkey": "Klávesa",
+  "keyboard_shortcuts.legend": "zobraziť túto legendu",
+  "keyboard_shortcuts.mention": "spomenúť autora",
+  "keyboard_shortcuts.reply": "odpovedať",
+  "keyboard_shortcuts.search": "zamerať sa na vyhľadávanie",
+  "keyboard_shortcuts.toot": "začať úplne novú hlášku",
+  "keyboard_shortcuts.unfocus": "nesústrediť sa na písaciu plochu, alebo hľadanie",
+  "keyboard_shortcuts.up": "posunúť sa vyššie v zozname",
+  "lightbox.close": "Zatvoriť",
+  "lightbox.next": "Ďalšie",
   "lightbox.previous": "Predchádzajúci",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
-  "lists.delete": "Delete list",
-  "lists.edit": "Edit list",
-  "lists.new.create": "Add list",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Your lists",
+  "lists.account.add": "Pridať do zoznamu",
+  "lists.account.remove": "Odobrať zo zoznamu",
+  "lists.delete": "Vymazať list",
+  "lists.edit": "Upraviť zoznam",
+  "lists.new.create": "Pridať zoznam",
+  "lists.new.title_placeholder": "Názov nového zoznamu",
+  "lists.search": "Vyhľadávajte medzi užívateľmi ktorých sledujete",
+  "lists.subheading": "Vaše zoznamy",
   "loading_indicator.label": "Nahrávam...",
   "media_gallery.toggle_visible": "Zapnúť/Vypnúť viditeľnosť",
   "missing_indicator.label": "Nenájdené",
-  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "missing_indicator.sublabel": "Tento zdroj sa nepodarilo nájsť",
+  "mute_modal.hide_notifications": "Skryť notifikácie od tohoto užívateľa?",
   "navigation_bar.blocks": "Blokovaní používatelia",
   "navigation_bar.community_timeline": "Lokálna časová os",
   "navigation_bar.edit_profile": "Upraviť profil",
   "navigation_bar.favourites": "Obľúbené",
-  "navigation_bar.follow_requests": "Žiadosti",
+  "navigation_bar.follow_requests": "Žiadosti o sledovanie",
   "navigation_bar.info": "O tomto Mastodon serveri",
-  "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
-  "navigation_bar.lists": "Lists",
+  "navigation_bar.keyboard_shortcuts": "Klávesové skratky",
+  "navigation_bar.lists": "Zoznamy",
   "navigation_bar.logout": "Odhlásiť",
   "navigation_bar.mutes": "Ignorovaní používatelia",
   "navigation_bar.pins": "Pripnuté toots",
   "navigation_bar.preferences": "Možnosti",
   "navigation_bar.public_timeline": "Federovaná časová os",
   "notification.favourite": "{name} sa páči tvoj status",
-  "notification.follow": "{name} ťa začal(a) sledovať",
-  "notification.mention": "{name} ťa zmienil",
+  "notification.follow": "{name} ťa začal/a následovať",
+  "notification.mention": "{name} ťa spomenul/a",
   "notification.reblog": "{name} re-tootol tvoj status",
-  "notifications.clear": "Vymazať notifikácie",
-  "notifications.clear_confirmation": "Naozaj chceš vymazať všetky tvoje notifikácie?",
-  "notifications.column_settings.alert": "Bublinové notifikácie",
+  "notifications.clear": "Vyčistiť zoznam notifikácii",
+  "notifications.clear_confirmation": "Naozaj chcete nenávratne prečistiť všetky vaše notifikácie?",
+  "notifications.column_settings.alert": "Notifikácie na ploche",
   "notifications.column_settings.favourite": "Obľúbené:",
-  "notifications.column_settings.follow": "Nový nasledujúci:",
+  "notifications.column_settings.follow": "Noví následujúci:",
   "notifications.column_settings.mention": "Zmienenia:",
   "notifications.column_settings.push": "Push notifikácie",
   "notifications.column_settings.push_meta": "Toto zariadenie",
-  "notifications.column_settings.reblog": "Re-toots:",
+  "notifications.column_settings.reblog": "Boosty:",
   "notifications.column_settings.show": "Zobraziť v stĺpci",
   "notifications.column_settings.sound": "Prehrať zvuk",
   "onboarding.done": "Koniec",
   "onboarding.next": "Ďalej",
-  "onboarding.page_five.public_timelines": "Lokálna časová os zobrazuje verejné správy od všetkých na {domain}. Federovaná časová os zobrazuje verejné správy od všetkých ľudí ktoré {domain} nasleduje. Tieto sú takzvané Verejné Časové Osi, výborná možnosť ako nájsť a spoznať nových ľudí.",
-  "onboarding.page_four.home": "Domovská časová os zobrazí správy od ľudí ktorých sleduješ.",
-  "onboarding.page_four.notifications": "Stĺpec s notifikáciami zobrazí keď budeš s niekým komunikovať.",
-  "onboarding.page_one.federation": "Mastodon je sieť nezávislých serverov spojením ktorých vzniká jedna veľká federovaná sociálna sieť.",
-  "onboarding.page_one.handle": "Ty si na {domain}, takže tvoje celý nickname je {handle}",
-  "onboarding.page_one.welcome": "Vitajte v Mastodon!",
+  "onboarding.page_five.public_timelines": "Lokálna časová os zobrazuje verejné správy od všetkých na {domain}. Federovaná časová os zobrazuje verejné správy od všetkých tých, čo následujú užívatrľov {domain} z iných serverov. Tieto sú takzvané Verejné Časové Osi, výborná možnosť ako nájsť a spoznať nových ľudí.",
+  "onboarding.page_four.home": "Domovská časová os zobrazí správy od ľudí ktorých sledujete.",
+  "onboarding.page_four.notifications": "Stĺpec s notifikáciami zobrazí keď budete s niekým komunikovať.",
+  "onboarding.page_one.federation": "Mastodon je sieť nezávislých serverov, spojením ktorých vzniká jedna veľká federovaná sociálna sieť.",
+  "onboarding.page_one.full_handle": "Vaša celá prezývka aj s adresou",
+  "onboarding.page_one.handle_hint": "Toto je čo by ste povedali vaším priateľom že majú hľadať.",
+  "onboarding.page_one.welcome": "Vitajte na Mastodone!",
   "onboarding.page_six.admin": "Správca tohto servera je {admin}.",
   "onboarding.page_six.almost_done": "Takmer hotovo...",
   "onboarding.page_six.appetoot": "Bon Appetoot!",
   "onboarding.page_six.apps_available": "Aplikácie {apps} sú dostupné na pre iOS, Android and ďalšie platformy.",
-  "onboarding.page_six.github": "Mastodon je free open-source software. Chyby, nové funkcie alebo prispievať svojím kódom mǒžeš na {github}.",
+  "onboarding.page_six.github": "Mastodon je free open-source software. Nahlásiť chyby, zaujímať sa o nové funkcie, alebo prispievať svojím kódom mǒžeete na {github}.",
   "onboarding.page_six.guidelines": "pravidlá komunity",
   "onboarding.page_six.read_guidelines": "Prosím prečítajte si {domain} pravidlá {guidelines}!",
   "onboarding.page_six.various_app": "mobilné applikácie",
-  "onboarding.page_three.profile": "Uprav svoj profile a zmeň svoj avatar, bio a meno ktoré bude zobrazené. V nastaveniach nájdeš ďalšie možnosti.",
-  "onboarding.page_three.search": "Použi vyhľadávacie políčko na nájdenie ľudí a hashtagov, ako napríklad {slovensko}, {slovakia} alebo {pivo}. Na nájdenie človeka ktorý je registrovaný na inom Mastodon serveri použi jeho celý nickname.",
-  "onboarding.page_two.compose": "Správy píš zo stĺpca na komponovanie. Môžeš nahrávať obrázky, meniť nastavenia súkromia správ a pridávať varovania ikonkami nižšie.",
+  "onboarding.page_three.profile": "Upravte svoj profil ak chcete zmeňiť svoj avatar, popis profilu a meno ktoré bude zobrazené. V nastaveniach nájdete ďalšie možnosti.",
+  "onboarding.page_three.search": "Použite vyhľadávacie políčko na nájdenie ľudí a hashtagov, ako napríklad {slovensko}, {slovakia} alebo {pivo}. Na nájdenie človeka ktorý je registrovaný na inom Mastodon serveri použi jeho celý nickname.",
+  "onboarding.page_two.compose": "Správy píšte zo stĺpca na komponovanie. Je možné nahrávať obrázky, meniť nastavenia súkromia správ a pridávať varovania ikonkami nižšie.",
   "onboarding.skip": "Preskočiť",
-  "privacy.change": "Zmeň viditeľnosť statusu",
-  "privacy.direct.long": "Pošli priamo iba spomenutým používateľom",
+  "privacy.change": "Zmeňiť viditeľnosť statusu",
+  "privacy.direct.long": "Poslať priamo iba spomenutým používateľom",
   "privacy.direct.short": "Súkromne",
-  "privacy.private.long": "Pošli iba sledujúcim",
-  "privacy.private.short": "Iba sledujúci",
-  "privacy.public.long": "Pošli všetkým",
-  "privacy.public.short": "Verejne",
+  "privacy.private.long": "Poslať iba následovateľom",
+  "privacy.private.short": "Iba pre sledujúcich",
+  "privacy.public.long": "Poslať všetkým verejne",
+  "privacy.public.short": "Verejné",
   "privacy.unlisted.long": "Neposielať do verejných časových osí",
-  "privacy.unlisted.short": "Verejne mimo osí",
+  "privacy.unlisted.short": "Verejne, ale nezobraziť v osi",
+  "regeneration_indicator.label": "Načítava sa…",
+  "regeneration_indicator.sublabel": "Vaša domovská nástenka sa pripravuje!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
-  "relative_time.just_now": "now",
+  "relative_time.just_now": "teraz",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Zrušiť",
+  "report.forward": "Posuň ku {target}",
+  "report.forward_hint": "Tento účet je z iného serveru. Chceš poslať anonymnú kópiu reportu aj tam?",
+  "report.hint": "Toto nahlásenie bude zaslané správcom servera. Môžeš napísať odvôvodnenie prečo si nahlásil/a tento účet:",
   "report.placeholder": "Ďalšie komentáre",
   "report.submit": "Poslať",
-  "report.target": "Reportovať {target}",
-  "search.placeholder": "Hľadaj",
-  "search_popout.search_format": "Advanced search format",
-  "search_popout.tips.hashtag": "hashtag",
+  "report.target": "Nahlásenie {target}",
+  "search.placeholder": "Hľadať",
+  "search_popout.search_format": "Pokročilý formát vyhľadávania",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
+  "search_popout.tips.hashtag": "haštag",
   "search_popout.tips.status": "status",
-  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
-  "search_popout.tips.user": "user",
-  "search_results.total": "{count, number} nájdených",
-  "standalone.public_title": "Čo tam nájdeš...",
-  "status.block": "Block @{name}",
+  "search_popout.tips.text": "Jednoduchý text vráti zhodujúce sa mená, prezývky a hashtagy",
+  "search_popout.tips.user": "používateľ",
+  "search_results.accounts": "Ľudia",
+  "search_results.hashtags": "Haštagy",
+  "search_results.statuses": "Príspevky",
+  "search_results.total": "{count, number} {count, plural, one {result} ostatné {results}}",
+  "standalone.public_title": "Pohľad dovnútra...",
+  "status.block": "Blokovať @{name}",
   "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý",
   "status.delete": "Zmazať",
-  "status.embed": "Embed",
+  "status.embed": "Vložiť",
   "status.favourite": "Páči sa mi",
-  "status.load_more": "Zobraziť viac",
+  "status.load_more": "Zobraz viac",
   "status.media_hidden": "Skryté médiá",
   "status.mention": "Napísať @{name}",
-  "status.more": "More",
-  "status.mute": "Mute @{name}",
+  "status.more": "Viac",
+  "status.mute": "Utíšiť @{name}",
   "status.mute_conversation": "Ignorovať konverzáciu",
-  "status.open": "Otvoriť",
+  "status.open": "Otvoriť tento status",
   "status.pin": "Pripnúť na profil",
-  "status.reblog": "Re-toot",
-  "status.reblogged_by": "{name} re-tootol",
+  "status.pinned": "Pripnutý príspevok",
+  "status.reblog": "Povýšiť",
+  "status.reblogged_by": "{name} povýšil",
   "status.reply": "Odpovedať",
-  "status.replyAll": "Odpovedať všetkým",
+  "status.replyAll": "Odpovedať na diskusiu",
   "status.report": "Nahlásiť @{name}",
-  "status.sensitive_toggle": "Klikni pre zobrazenie",
+  "status.sensitive_toggle": "Kliknite pre zobrazenie",
   "status.sensitive_warning": "Chúlostivý obsah",
   "status.share": "Zdieľať",
-  "status.show_less": "Zobraziť menej",
-  "status.show_more": "Zobraziť viac",
+  "status.show_less": "Zobraz menej",
+  "status.show_more": "Zobraz viac",
   "status.unmute_conversation": "Prestať ignorovať konverzáciu",
   "status.unpin": "Odopnúť z profilu",
-  "tabs_bar.compose": "Napísať",
   "tabs_bar.federated_timeline": "Federovaná",
   "tabs_bar.home": "Domov",
-  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.local_timeline": "Lokálna",
   "tabs_bar.notifications": "Notifikácie",
-  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "ui.beforeunload": "Čo máte rozpísané sa stratí, ak opustíte Mastodon.",
   "upload_area.title": "Ťahaj a pusti pre nahratie",
-  "upload_button.label": "Pridať",
-  "upload_form.description": "Describe for the visually impaired",
-  "upload_form.undo": "Späť",
-  "upload_progress.label": "Nahrávam...",
+  "upload_button.label": "Pridať médiá",
+  "upload_form.description": "Opis pre slabo vidiacich",
+  "upload_form.focus": "Vystrihni",
+  "upload_form.undo": "Navrátiť",
+  "upload_progress.label": "Nahráva sa...",
   "video.close": "Zavrieť video",
   "video.exit_fullscreen": "Vpnúť zobrazenie na celú obrazovku",
   "video.expand": "Zväčšiť video",
-  "video.fullscreen": "Zapnúť zobrazenie na celú obrazovku",
+  "video.fullscreen": "Zobraziť na celú obrazovku",
   "video.hide": "Skryť video",
   "video.mute": "Vypnúť zvuk",
   "video.pause": "Pauza",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index 88631e332..a672ae6ca 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Blokiraj korisnika @{name}",
   "account.block_domain": "Sakrij sve sa domena {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Navedene informacije možda ne odslikavaju korisnički profil u potpunosti.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Izmeni profil",
   "account.follow": "Zaprati",
   "account.followers": "Pratioca",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} se pomerio na:",
   "account.mute": "Ućutkaj korisnika @{name}",
   "account.mute_notifications": "Isključi obaveštenja od korisnika @{name}",
+  "account.muted": "Muted",
   "account.posts": "Statusa",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Prijavi @{name}",
   "account.requested": "Čekam odobrenje. Kliknite da poništite zahtev za praćenje",
   "account.share": "Podeli profil korisnika @{name}",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Učitavam...",
   "media_gallery.toggle_visible": "Uključi/isključi vidljivost",
   "missing_indicator.label": "Nije pronađeno",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Sakrij obaveštenja od ovog korisnika?",
   "navigation_bar.blocks": "Blokirani korisnici",
   "navigation_bar.community_timeline": "Lokalna lajna",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "Početna lajna prikazuje statuse ljudi koje Vi pratite.",
   "onboarding.page_four.notifications": "Kolona sa obaveštenjima Vam prikazuje kada neko priča sa Vama.",
   "onboarding.page_one.federation": "Mastodont je mreža nezavisnih servera koji se uvezuju da naprave jednu veću društvenu mrežu. Ove servere zovemo instancama.",
-  "onboarding.page_one.handle": "Vi ste na domenu {domain}, pa je Vaša puna identifikacija {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Dobrodošli na Mastodont!",
   "onboarding.page_six.admin": "Administrator Vaše instance je {admin}.",
   "onboarding.page_six.almost_done": "Još malo, pa gotovo...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Javno",
   "privacy.unlisted.long": "Ne objavljuj na javnim lajnama",
   "privacy.unlisted.short": "Neizlistano",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "sada",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Poništi",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Dodatni komentari",
   "report.submit": "Pošalji",
   "report.target": "Prijavljujem {target}",
   "search.placeholder": "Pretraga",
   "search_popout.search_format": "Napredni format pretrage",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hešteg",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Traženjem običnog teksta ćete dobiti sva pronađena imena, sva korisnička imena i sve nađene heštegove",
   "search_popout.tips.user": "korisnik",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {rezultat} few {rezultata} other {rezultata}}",
   "standalone.public_title": "Pogled iznutra...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Ućutkaj prepisku",
   "status.open": "Proširi ovaj status",
   "status.pin": "Prikači na profil",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Podrži",
   "status.reblogged_by": "{name} podržao(la)",
   "status.reply": "Odgovori",
@@ -239,7 +255,6 @@
   "status.show_more": "Prikaži više",
   "status.unmute_conversation": "Uključi prepisku",
   "status.unpin": "Otkači sa profila",
-  "tabs_bar.compose": "Napiši",
   "tabs_bar.federated_timeline": "Federisano",
   "tabs_bar.home": "Početna",
   "tabs_bar.local_timeline": "Lokalno",
@@ -248,6 +263,7 @@
   "upload_area.title": "Prevucite ovde da otpremite",
   "upload_button.label": "Dodaj multimediju",
   "upload_form.description": "Opiši za slabovide osobe",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Opozovi",
   "upload_progress.label": "Otpremam...",
   "video.close": "Zatvori video",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index e65c02ab7..1e3a3ce2b 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Блокирај корисника @{name}",
   "account.block_domain": "Сакриј све са домена {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Наведене информације можда не одсликавају кориснички профил у потпуности.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Измени профил",
   "account.follow": "Запрати",
   "account.followers": "Пратиоца",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} се померио на:",
   "account.mute": "Ућуткај корисника @{name}",
   "account.mute_notifications": "Искључи обавештења од корисника @{name}",
+  "account.muted": "Muted",
   "account.posts": "Статуса",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Пријави @{name}",
   "account.requested": "Чекам одобрење. Кликните да поништите захтев за праћење",
   "account.share": "Подели профил корисника @{name}",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Учитавам...",
   "media_gallery.toggle_visible": "Укључи/искључи видљивост",
   "missing_indicator.label": "Није пронађено",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Сакриј обавештења од овог корисника?",
   "navigation_bar.blocks": "Блокирани корисници",
   "navigation_bar.community_timeline": "Локална лајна",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "Почетна лајна приказује статусе људи које Ви пратите.",
   "onboarding.page_four.notifications": "Колона са обавештењима Вам приказује када неко прича са Вама.",
   "onboarding.page_one.federation": "Мастодонт је мрежа независних сервера који се увезују да направе једну већу друштвену мрежу. Ове сервере зовемо инстанцама.",
-  "onboarding.page_one.handle": "Ви сте на домену {domain}, па је Ваша пуна идентификација {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Добродошли на Мастодонт!",
   "onboarding.page_six.admin": "Администратор Ваше инстанце је {admin}.",
   "onboarding.page_six.almost_done": "Још мало, па готово...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Јавно",
   "privacy.unlisted.long": "Не објављуј на јавним лајнама",
   "privacy.unlisted.short": "Неизлистано",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "сада",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Поништи",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Додатни коментари",
   "report.submit": "Пошаљи",
   "report.target": "Пријављујем {target}",
   "search.placeholder": "Претрага",
   "search_popout.search_format": "Напредни формат претраге",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "хештег",
   "search_popout.tips.status": "статус",
   "search_popout.tips.text": "Тражењем обичног текста ћете добити сва пронађена имена, сва корисничка имена и све нађене хештегове",
   "search_popout.tips.user": "корисник",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {резултат} few {резултата} other {резултата}}",
   "standalone.public_title": "Поглед изнутра...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Ућуткај преписку",
   "status.open": "Прошири овај статус",
   "status.pin": "Прикачи на профил",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Подржи",
   "status.reblogged_by": "{name} подржао(ла)",
   "status.reply": "Одговори",
@@ -239,7 +255,6 @@
   "status.show_more": "Прикажи више",
   "status.unmute_conversation": "Укључи преписку",
   "status.unpin": "Откачи са профила",
-  "tabs_bar.compose": "Напиши",
   "tabs_bar.federated_timeline": "Федерисано",
   "tabs_bar.home": "Почетна",
   "tabs_bar.local_timeline": "Локално",
@@ -248,6 +263,7 @@
   "upload_area.title": "Превуците овде да отпремите",
   "upload_button.label": "Додај мултимедију",
   "upload_form.description": "Опиши за слабовиде особе",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Опозови",
   "upload_progress.label": "Отпремам...",
   "video.close": "Затвори видео",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index edfa9b8c2..9c51d5b36 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -1,28 +1,32 @@
 {
   "account.block": "Blockera @{name}",
   "account.block_domain": "Dölj allt från {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Informationen nedan kan spegla användarens profil ofullständigt.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Redigera profil",
   "account.follow": "Följ",
   "account.followers": "Följare",
   "account.follows": "Följer",
   "account.follows_you": "Följer dig",
-  "account.hide_reblogs": "Hide boosts from @{name}",
+  "account.hide_reblogs": "Dölj knuffar från @{name}",
   "account.media": "Media",
   "account.mention": "Nämna @{name}",
-  "account.moved_to": "{name} has moved to:",
+  "account.moved_to": "{name} har flyttat till:",
   "account.mute": "Tysta @{name}",
-  "account.mute_notifications": "Mute notifications from @{name}",
+  "account.mute_notifications": "Stäng av notifieringar från @{name}",
+  "account.muted": "Muted",
   "account.posts": "Inlägg",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Rapportera @{name}",
   "account.requested": "Inväntar godkännande. Klicka för att avbryta följförfrågan",
   "account.share": "Dela @{name}'s profil",
-  "account.show_reblogs": "Show boosts from @{name}",
+  "account.show_reblogs": "Visa knuffar från @{name}",
   "account.unblock": "Avblockera @{name}",
   "account.unblock_domain": "Ta fram {domain}",
   "account.unfollow": "Sluta följa",
   "account.unmute": "Ta bort tystad @{name}",
-  "account.unmute_notifications": "Unmute notifications from @{name}",
+  "account.unmute_notifications": "Återaktivera notifikationer från @{name}",
   "account.view_full_profile": "Visa hela profilen",
   "boost_modal.combo": "Du kan trycka {combo} för att slippa denna nästa gång",
   "bundle_column_error.body": "Något gick fel när du laddade denna komponent.",
@@ -36,7 +40,7 @@
   "column.favourites": "Favoriter",
   "column.follow_requests": "Följ förfrågningar",
   "column.home": "Hem",
-  "column.lists": "Lists",
+  "column.lists": "Listor",
   "column.mutes": "Tystade användare",
   "column.notifications": "Meddelanden",
   "column.pins": "Nålade toots",
@@ -50,7 +54,7 @@
   "column_header.unpin": "Ångra fäst",
   "column_subheading.navigation": "Navigation",
   "column_subheading.settings": "Inställningar",
-  "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
+  "compose_form.hashtag_warning": "Denna toot kommer inte att listas under någon hashtag eftersom den är onoterad. Endast offentliga toots kan sökas med hashtag.",
   "compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vemsomhelst kan följa dig och även se dina inlägg skrivna för endast dina följare.",
   "compose_form.lock_disclaimer.lock": "låst",
   "compose_form.placeholder": "Vad funderar du på?",
@@ -65,7 +69,7 @@
   "confirmations.delete.confirm": "Ta bort",
   "confirmations.delete.message": "Är du säker att du vill ta bort denna status?",
   "confirmations.delete_list.confirm": "Delete",
-  "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
+  "confirmations.delete_list.message": "Är du säker på att du vill radera denna lista permanent?",
   "confirmations.domain_block.confirm": "Blockera hela domänen",
   "confirmations.domain_block.message": "Är du verkligen, verkligen säker på att du vill blockera hela {domain}? I de flesta fall är några riktade blockeringar eller nedtystade tillräckligt och föredras.",
   "confirmations.mute.confirm": "Tysta",
@@ -92,7 +96,7 @@
   "empty_column.hashtag": "Det finns inget i denna hashtag ännu.",
   "empty_column.home": "Din hemma-tidslinje är tom! Besök {public} eller använd sökning för att komma igång och träffa andra användare.",
   "empty_column.home.public_timeline": "den publika tidslinjen",
-  "empty_column.list": "There is nothing in this list yet.",
+  "empty_column.list": "Det finns inget i denna lista än. När medlemmar i denna lista lägger till nya statusar kommer de att visas här.",
   "empty_column.notifications": "Du har inga meddelanden än. Interagera med andra för att starta konversationen.",
   "empty_column.public": "Det finns inget här! Skriv något offentligt, eller följ manuellt användarna från andra instanser för att fylla på det",
   "follow_request.authorize": "Godkänn",
@@ -108,46 +112,47 @@
   "home.column_settings.show_reblogs": "Visa knuffar",
   "home.column_settings.show_replies": "Visa svar",
   "home.settings": "Kolumninställningar",
-  "keyboard_shortcuts.back": "to navigate back",
-  "keyboard_shortcuts.boost": "to boost",
-  "keyboard_shortcuts.column": "to focus a status in one of the columns",
-  "keyboard_shortcuts.compose": "to focus the compose textarea",
+  "keyboard_shortcuts.back": "att navigera tillbaka",
+  "keyboard_shortcuts.boost": "att knuffa",
+  "keyboard_shortcuts.column": "att fokusera en status i en av kolumnerna",
+  "keyboard_shortcuts.compose": "att fokusera komponera text fältet",
   "keyboard_shortcuts.description": "Description",
-  "keyboard_shortcuts.down": "to move down in the list",
+  "keyboard_shortcuts.down": "att flytta ner i listan",
   "keyboard_shortcuts.enter": "to open status",
-  "keyboard_shortcuts.favourite": "to favourite",
+  "keyboard_shortcuts.favourite": "att favorisera",
   "keyboard_shortcuts.heading": "Keyboard Shortcuts",
-  "keyboard_shortcuts.hotkey": "Hotkey",
-  "keyboard_shortcuts.legend": "to display this legend",
-  "keyboard_shortcuts.mention": "to mention author",
-  "keyboard_shortcuts.reply": "to reply",
-  "keyboard_shortcuts.search": "to focus search",
-  "keyboard_shortcuts.toot": "to start a brand new toot",
-  "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
-  "keyboard_shortcuts.up": "to move up in the list",
+  "keyboard_shortcuts.hotkey": "Snabbvalstangent",
+  "keyboard_shortcuts.legend": "att visa denna översikt",
+  "keyboard_shortcuts.mention": "att nämna författaren",
+  "keyboard_shortcuts.reply": "att svara",
+  "keyboard_shortcuts.search": "att fokusera sökfältet",
+  "keyboard_shortcuts.toot": "att börja en helt ny toot",
+  "keyboard_shortcuts.unfocus": "att avfokusera komponera text fält / sökfält",
+  "keyboard_shortcuts.up": "att flytta upp i listan",
   "lightbox.close": "Stäng",
   "lightbox.next": "Nästa",
   "lightbox.previous": "Tidigare",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
-  "lists.delete": "Delete list",
-  "lists.edit": "Edit list",
-  "lists.new.create": "Add list",
-  "lists.new.title_placeholder": "New list title",
-  "lists.search": "Search among people you follow",
-  "lists.subheading": "Your lists",
+  "lists.account.add": "Lägg till i lista",
+  "lists.account.remove": "Ta bort från lista",
+  "lists.delete": "Radera lista",
+  "lists.edit": "Redigera lista",
+  "lists.new.create": "Lägg till lista",
+  "lists.new.title_placeholder": "Ny listrubrik",
+  "lists.search": "Sök bland personer du följer",
+  "lists.subheading": "Dina listor",
   "loading_indicator.label": "Laddar...",
   "media_gallery.toggle_visible": "Växla synlighet",
   "missing_indicator.label": "Hittades inte",
-  "mute_modal.hide_notifications": "Hide notifications from this user?",
+  "missing_indicator.sublabel": "Den här resursen kunde inte hittas",
+  "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.edit_profile": "Redigera profil",
   "navigation_bar.favourites": "Favoriter",
   "navigation_bar.follow_requests": "Följförfrågningar",
   "navigation_bar.info": "Om denna instans",
-  "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
-  "navigation_bar.lists": "Lists",
+  "navigation_bar.keyboard_shortcuts": "Tangentbordsgenvägar",
+  "navigation_bar.lists": "Listor",
   "navigation_bar.logout": "Logga ut",
   "navigation_bar.mutes": "Tystade användare",
   "navigation_bar.pins": "Nålade inlägg (toots)",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "Hemmatidslinjen visar inlägg från personer du följer.",
   "onboarding.page_four.notifications": "Meddelandekolumnen visar när någon interagerar med dig.",
   "onboarding.page_one.federation": "Mastodon är ett nätverk av oberoende servrar som ansluter för att skapa ett större socialt nätverk. Vi kallar dessa servrar instanser.",
-  "onboarding.page_one.handle": "Du är på {domain}, så din fulla hantering är {handle}",
+  "onboarding.page_one.full_handle": "Ditt fullständiga användarnamn/mastodonadress",
+  "onboarding.page_one.handle_hint": "Det här är vad du skulle berätta för dina vänner att söka efter.",
   "onboarding.page_one.welcome": "Välkommen till Mastodon!",
   "onboarding.page_six.admin": "Din instansadmin är {admin}.",
   "onboarding.page_six.almost_done": "Snart klart...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Publik",
   "privacy.unlisted.long": "Skicka inte till publik tidslinje",
   "privacy.unlisted.short": "Olistad",
+  "regeneration_indicator.label": "Laddar…",
+  "regeneration_indicator.sublabel": "Ditt hemmaflöde förbereds!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
-  "relative_time.just_now": "now",
+  "relative_time.just_now": "nu",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Ångra",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Ytterligare kommentarer",
   "report.submit": "Skicka",
   "report.target": "Rapporterar {target}",
   "search.placeholder": "Sök",
   "search_popout.search_format": "Avancerat sökformat",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Enkel text returnerar matchande visningsnamn, användarnamn och hashtags",
   "search_popout.tips.user": "användare",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, ett {result} andra {results}}",
   "standalone.public_title": "En titt inuti...",
   "status.block": "Block @{name}",
@@ -222,11 +237,12 @@
   "status.load_more": "Ladda fler",
   "status.media_hidden": "Media dold",
   "status.mention": "Omnämn @{name}",
-  "status.more": "More",
-  "status.mute": "Mute @{name}",
+  "status.more": "Mer",
+  "status.mute": "Tysta @{name}",
   "status.mute_conversation": "Tysta konversation",
   "status.open": "Utvidga denna status",
   "status.pin": "Fäst i profil",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Knuff",
   "status.reblogged_by": "{name} knuffade",
   "status.reply": "Svara",
@@ -239,15 +255,15 @@
   "status.show_more": "Visa mer",
   "status.unmute_conversation": "Öppna konversation",
   "status.unpin": "Ångra fäst i profil",
-  "tabs_bar.compose": "Skriv",
   "tabs_bar.federated_timeline": "Förenad",
   "tabs_bar.home": "Hem",
   "tabs_bar.local_timeline": "Lokal",
   "tabs_bar.notifications": "Meddelanden",
-  "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
+  "ui.beforeunload": "Ditt utkast kommer att förloras om du lämnar Mastodon.",
   "upload_area.title": "Dra & släpp för att ladda upp",
   "upload_button.label": "Lägg till media",
   "upload_form.description": "Beskriv för synskadade",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Ångra",
   "upload_progress.label": "Laddar upp...",
   "video.close": "Stäng video",
@@ -255,7 +271,7 @@
   "video.expand": "Expandera video",
   "video.fullscreen": "Helskärm",
   "video.hide": "Dölj video",
-  "video.mute": "Tysta ljud",
+  "video.mute": "Stäng av ljud",
   "video.pause": "Pause",
   "video.play": "Spela upp",
   "video.unmute": "Spela upp ljud"
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 06323ebfc..cab2ce089 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Block @{name}",
   "account.block_domain": "Hide everything from {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Edit profile",
   "account.follow": "Follow",
   "account.followers": "Followers",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "Mute @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
   "account.posts": "Posts",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Report @{name}",
   "account.requested": "Awaiting approval",
   "account.share": "Share @{name}'s profile",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Loading...",
   "media_gallery.toggle_visible": "Toggle visibility",
   "missing_indicator.label": "Not found",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
   "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
   "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
-  "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Welcome to Mastodon!",
   "onboarding.page_six.admin": "Your instance's admin is {admin}.",
   "onboarding.page_six.almost_done": "Almost done...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Do not post to public timelines",
   "privacy.unlisted.short": "Unlisted",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancel",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Additional comments",
   "report.submit": "Submit",
   "report.target": "Reporting",
   "search.placeholder": "Search",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Mute conversation",
   "status.open": "Expand this status",
   "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Boost",
   "status.reblogged_by": "{name} boosted",
   "status.reply": "Reply",
@@ -239,7 +255,6 @@
   "status.show_more": "Show more",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Compose",
   "tabs_bar.federated_timeline": "Federated",
   "tabs_bar.home": "Home",
   "tabs_bar.local_timeline": "Local",
@@ -248,6 +263,7 @@
   "upload_area.title": "Drag & drop to upload",
   "upload_button.label": "Add media",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Undo",
   "upload_progress.label": "Uploading...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index ce6434ca6..83c10de34 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Engelle @{name}",
   "account.block_domain": "Hide everything from {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Profili düzenle",
   "account.follow": "Takip et",
   "account.followers": "Takipçiler",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "Sustur @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
   "account.posts": "Gönderiler",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Rapor et @{name}",
   "account.requested": "Onay bekleniyor",
   "account.share": "Share @{name}'s profile",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Yükleniyor...",
   "media_gallery.toggle_visible": "Görünürlüğü değiştir",
   "missing_indicator.label": "Bulunamadı",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Engellenen kullanıcılar",
   "navigation_bar.community_timeline": "Yerel zaman tüneli",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "Takip ettiğiniz insanlardan gelen gönderileri gosteren zaman tünelidir",
   "onboarding.page_four.notifications": "Herkimse sizinle iletişime geçtiğinde gelen bildirimleri gösterir.",
   "onboarding.page_one.federation": "Mastodon, geniş bir sosyal ağ kurmak için birleşen bağımsız sunuculardan oluşan bir ağdır.",
-  "onboarding.page_one.handle": "{domain} sunucundasınız, bu yüzden tüm kontrol {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Mastodon'a hoş geldiniz.",
   "onboarding.page_six.admin": "{admin}, şu anda bulunduğunuz sunucunun yöneticisidir.",
   "onboarding.page_six.almost_done": "Neredeyse tamam...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Herkese açık",
   "privacy.unlisted.long": "Herkese açık zaman tüneline gönderme",
   "privacy.unlisted.short": "Listelenmemiş",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "İptal",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Ek yorumlar",
   "report.submit": "Gönder",
   "report.target": "Raporlama",
   "search.placeholder": "Ara",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {sonuç} other {sonuçlar}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Mute conversation",
   "status.open": "Bu gönderiyi genişlet",
   "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Boost'la",
   "status.reblogged_by": "{name} boost etti",
   "status.reply": "Cevapla",
@@ -239,7 +255,6 @@
   "status.show_more": "Daha fazlası",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Oluştur",
   "tabs_bar.federated_timeline": "Federe",
   "tabs_bar.home": "Ana sayfa",
   "tabs_bar.local_timeline": "Yerel",
@@ -248,6 +263,7 @@
   "upload_area.title": "Upload için sürükle bırak yapınız",
   "upload_button.label": "Görsel ekle",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Geri al",
   "upload_progress.label": "Yükleniyor...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 46d22ac83..b49f707e1 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -1,7 +1,9 @@
 {
   "account.block": "Заблокувати",
   "account.block_domain": "Заглушити {domain}",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "Налаштування профілю",
   "account.follow": "Підписатися",
   "account.followers": "Підписники",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "Заглушити",
   "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
   "account.posts": "Пости",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "Поскаржитися",
   "account.requested": "Очікує підтвердження",
   "account.share": "Share @{name}'s profile",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "Завантаження...",
   "media_gallery.toggle_visible": "Показати/приховати",
   "missing_indicator.label": "Не знайдено",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Заблоковані користувачі",
   "navigation_bar.community_timeline": "Локальна стрічка",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "Домашня стрічка показує пости користувачів, на яких Ви підписані.",
   "onboarding.page_four.notifications": "Колонка сповіщень показує моменти, коли хтось звертається до Вас.",
   "onboarding.page_one.federation": "Mastodon - це мережа незалежних серверів, які разом образовують єдину соціальну мережу. Ми называємо ці сервери інстанціями.",
-  "onboarding.page_one.handle": "Ви знаходитесь на домені {domain}, тому Ваш повний нік - {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "Ласкаво просимо до Mastodon!",
   "onboarding.page_six.admin": "Адміністратором Вашої інстанції є {admin}.",
   "onboarding.page_six.almost_done": "Майже готово...",
@@ -197,21 +203,30 @@
   "privacy.public.short": "Публічний",
   "privacy.unlisted.long": "Не показувати у публічних стрічках",
   "privacy.unlisted.short": "Прихований",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Відмінити",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Додаткові коментарі",
   "report.submit": "Відправити",
   "report.target": "Скаржимося на",
   "search.placeholder": "Пошук",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} {count, plural, one {результат} few {результати} many {результатів} other {результатів}}",
   "standalone.public_title": "A look inside...",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "Заглушити діалог",
   "status.open": "Розгорнути допис",
   "status.pin": "Pin on profile",
+  "status.pinned": "Pinned toot",
   "status.reblog": "Передмухнути",
   "status.reblogged_by": "{name} передмухнув(-ла)",
   "status.reply": "Відповісти",
@@ -239,7 +255,6 @@
   "status.show_more": "Розгорнути",
   "status.unmute_conversation": "Зняти глушення з діалогу",
   "status.unpin": "Unpin from profile",
-  "tabs_bar.compose": "Написати",
   "tabs_bar.federated_timeline": "Глобальна",
   "tabs_bar.home": "Головна",
   "tabs_bar.local_timeline": "Локальна",
@@ -248,6 +263,7 @@
   "upload_area.title": "Перетягніть сюди, щоб завантажити",
   "upload_button.label": "Додати медіаконтент",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "Відмінити",
   "upload_progress.label": "Завантаження...",
   "video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/whitelist_hy.json b/app/javascript/mastodon/locales/whitelist_hy.json
new file mode 100644
index 000000000..0d4f101c7
--- /dev/null
+++ b/app/javascript/mastodon/locales/whitelist_hy.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index a02211b8a..5ccfbc4f4 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -1,7 +1,9 @@
 {
   "account.block": "屏蔽 @{name}",
   "account.block_domain": "隐藏来自 {domain} 的内容",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "此处显示的信息可能不是全部内容。",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "修改个人资料",
   "account.follow": "关注",
   "account.followers": "关注者",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} 已经迁移到:",
   "account.mute": "隐藏 @{name}",
   "account.mute_notifications": "隐藏来自 @{name} 的通知",
+  "account.muted": "Muted",
   "account.posts": "嘟文",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "举报 @{name}",
   "account.requested": "正在等待对方同意。点击以取消发送关注请求",
   "account.share": "分享 @{name} 的个人资料",
@@ -207,15 +211,22 @@
   "relative_time.minutes": "{number}分",
   "relative_time.seconds": "{number}秒",
   "reply_indicator.cancel": "取消",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "附言",
   "report.submit": "提交",
   "report.target": "举报 {target}",
   "search.placeholder": "搜索",
   "search_popout.search_format": "高级搜索格式",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "话题标签",
   "search_popout.tips.status": "嘟文",
   "search_popout.tips.text": "使用普通字符进行搜索将会返回昵称、用户名和话题标签",
   "search_popout.tips.user": "用户",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "共 {count, number} 个结果",
   "standalone.public_title": "大家都在干啥?",
   "status.block": "屏蔽 @{name}",
@@ -231,6 +242,7 @@
   "status.mute_conversation": "隐藏此对话",
   "status.open": "展开嘟文",
   "status.pin": "在个人资料页面置顶",
+  "status.pinned": "Pinned toot",
   "status.reblog": "转嘟",
   "status.reblogged_by": "{name} 转嘟了",
   "status.reply": "回复",
@@ -243,7 +255,6 @@
   "status.show_more": "显示内容",
   "status.unmute_conversation": "不再隐藏此对话",
   "status.unpin": "在个人资料页面取消置顶",
-  "tabs_bar.compose": "撰写",
   "tabs_bar.federated_timeline": "跨站",
   "tabs_bar.home": "主页",
   "tabs_bar.local_timeline": "本站",
@@ -252,6 +263,7 @@
   "upload_area.title": "将文件拖放到此处开始上传",
   "upload_button.label": "上传媒体文件",
   "upload_form.description": "为视觉障碍人士添加文字说明",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "取消上传",
   "upload_progress.label": "上传中…",
   "video.close": "关闭视频",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index 7745da622..b105fe426 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -1,7 +1,9 @@
 {
   "account.block": "封鎖 @{name}",
   "account.block_domain": "隱藏來自 {domain} 的一切文章",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "下列資料不一定完整。",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "修改個人資料",
   "account.follow": "關注",
   "account.followers": "關注的人",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "將 @{name} 靜音",
   "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
   "account.posts": "文章",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "舉報 @{name}",
   "account.requested": "等候審批",
   "account.share": "分享 @{name} 的個人資料",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "載入中...",
   "media_gallery.toggle_visible": "打開或關上",
   "missing_indicator.label": "找不到內容",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "被你封鎖的用戶",
   "navigation_bar.community_timeline": "本站時間軸",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "「主頁」顯示你所關注用戶的文章",
   "onboarding.page_four.notifications": "「通知」欄顯示你和其他人的互動。",
   "onboarding.page_one.federation": "Mastodon(萬象社交)是由一批獨立網站組成的龐大網絡,我們將這些獨立又互連網站稱為「服務站」(instance)",
-  "onboarding.page_one.handle": "你的帳戶在 {domain} 上面,由 {handle} 代理",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "歡迎使用 Mastodon(萬象社交)",
   "onboarding.page_six.admin": "你服務站的管理員是{admin}",
   "onboarding.page_six.almost_done": "差不多了……",
@@ -197,21 +203,30 @@
   "privacy.public.short": "公共",
   "privacy.unlisted.long": "公開,但不在公共時間軸顯示",
   "privacy.unlisted.short": "公開",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "取消",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "額外訊息",
   "report.submit": "提交",
   "report.target": "舉報",
   "search.placeholder": "搜尋",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} 項結果",
   "standalone.public_title": "站點一瞥…",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "靜音對話",
   "status.open": "展開文章",
   "status.pin": "置頂到資料頁",
+  "status.pinned": "Pinned toot",
   "status.reblog": "轉推",
   "status.reblogged_by": "{name} 轉推",
   "status.reply": "回應",
@@ -239,7 +255,6 @@
   "status.show_more": "顯示更多",
   "status.unmute_conversation": "解禁對話",
   "status.unpin": "解除置頂",
-  "tabs_bar.compose": "撰寫",
   "tabs_bar.federated_timeline": "跨站",
   "tabs_bar.home": "主頁",
   "tabs_bar.local_timeline": "本站",
@@ -248,6 +263,7 @@
   "upload_area.title": "將檔案拖放至此上載",
   "upload_button.label": "上載媒體檔案",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "還原",
   "upload_progress.label": "上載中……",
   "video.close": "關閉影片",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index 65b174ab5..b0a94f67b 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -1,7 +1,9 @@
 {
   "account.block": "封鎖 @{name}",
   "account.block_domain": "隱藏來自 {domain} 的一切貼文",
+  "account.blocked": "Blocked",
   "account.disclaimer_full": "下列資料不一定完整。",
+  "account.domain_blocked": "Domain hidden",
   "account.edit_profile": "編輯用者資訊",
   "account.follow": "關注",
   "account.followers": "專注者",
@@ -13,7 +15,9 @@
   "account.moved_to": "{name} has moved to:",
   "account.mute": "消音 @{name}",
   "account.mute_notifications": "Mute notifications from @{name}",
+  "account.muted": "Muted",
   "account.posts": "貼文",
+  "account.posts_with_replies": "Toots with replies",
   "account.report": "檢舉 @{name}",
   "account.requested": "正在等待許可",
   "account.share": "分享 @{name} 的用者資訊",
@@ -139,6 +143,7 @@
   "loading_indicator.label": "讀取中...",
   "media_gallery.toggle_visible": "切換可見性",
   "missing_indicator.label": "找不到",
+  "missing_indicator.sublabel": "This resource could not be found",
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "封鎖的使用者",
   "navigation_bar.community_timeline": "本地時間軸",
@@ -174,7 +179,8 @@
   "onboarding.page_four.home": "家時間軸顯示所有你關注的人的貼文。",
   "onboarding.page_four.notifications": "通知欄顯示別人和你的互動。",
   "onboarding.page_one.federation": "Mastodon 是由獨立的伺服器連結起來,形成的大社群網路。我們把這些伺服器稱為副本。",
-  "onboarding.page_one.handle": "你在 {domain} 上,所以你的帳號全名是 {handle}",
+  "onboarding.page_one.full_handle": "Your full handle",
+  "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
   "onboarding.page_one.welcome": "歡迎來到 Mastodon !",
   "onboarding.page_six.admin": "你的副本的管理員是 {admin} 。",
   "onboarding.page_six.almost_done": "快好了…",
@@ -197,21 +203,30 @@
   "privacy.public.short": "公開貼",
   "privacy.unlisted.long": "不要貼到公開時間軸",
   "privacy.unlisted.short": "不列出來",
+  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
   "relative_time.just_now": "now",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "取消",
+  "report.forward": "Forward to {target}",
+  "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
+  "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "更多訊息",
   "report.submit": "送出",
   "report.target": "通報中",
   "search.placeholder": "搜尋",
   "search_popout.search_format": "Advanced search format",
+  "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
   "search_popout.tips.status": "status",
   "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
   "search_popout.tips.user": "user",
+  "search_results.accounts": "People",
+  "search_results.hashtags": "Hashtags",
+  "search_results.statuses": "Toots",
   "search_results.total": "{count, number} 項結果",
   "standalone.public_title": "站點一瞥…",
   "status.block": "Block @{name}",
@@ -227,6 +242,7 @@
   "status.mute_conversation": "消音對話",
   "status.open": "展開這個狀態",
   "status.pin": "置頂到個人資訊頁",
+  "status.pinned": "Pinned toot",
   "status.reblog": "轉推",
   "status.reblogged_by": "{name} 轉推了",
   "status.reply": "回應",
@@ -239,7 +255,6 @@
   "status.show_more": "看更多",
   "status.unmute_conversation": "不消音對話",
   "status.unpin": "解除置頂",
-  "tabs_bar.compose": "編輯",
   "tabs_bar.federated_timeline": "聯盟",
   "tabs_bar.home": "家",
   "tabs_bar.local_timeline": "本地",
@@ -248,6 +263,7 @@
   "upload_area.title": "拖放來上傳",
   "upload_button.label": "增加媒體",
   "upload_form.description": "Describe for the visually impaired",
+  "upload_form.focus": "Crop",
   "upload_form.undo": "復原",
   "upload_progress.label": "上傳中...",
   "video.close": "關閉影片",
diff --git a/app/javascript/mastodon/reducers/accounts.js b/app/javascript/mastodon/reducers/accounts.js
index f77061dfa..47e6d2330 100644
--- a/app/javascript/mastodon/reducers/accounts.js
+++ b/app/javascript/mastodon/reducers/accounts.js
@@ -102,7 +102,7 @@ const initialState = ImmutableMap();
 export default function accounts(state = initialState, action) {
   switch(action.type) {
   case STORE_HYDRATE:
-    return state.merge(action.state.get('accounts'));
+    return normalizeAccounts(state, Object.values(action.state.get('accounts').toJS()));
   case ACCOUNT_FETCH_SUCCESS:
   case NOTIFICATIONS_UPDATE:
     return normalizeAccount(state, action.account);
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index c709fb88c..532f4b2a7 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -16,6 +16,8 @@ import {
   COMPOSE_SUGGESTIONS_CLEAR,
   COMPOSE_SUGGESTIONS_READY,
   COMPOSE_SUGGESTION_SELECT,
+  COMPOSE_SUGGESTION_TAGS_UPDATE,
+  COMPOSE_TAG_HISTORY_UPDATE,
   COMPOSE_SENSITIVITY_CHANGE,
   COMPOSE_SPOILERNESS_CHANGE,
   COMPOSE_SPOILER_TEXT_CHANGE,
@@ -34,7 +36,7 @@ import uuid from '../uuid';
 import { me } from '../initial_state';
 
 const initialState = ImmutableMap({
-  mounted: false,
+  mounted: 0,
   sensitive: false,
   spoiler: false,
   spoiler_text: '',
@@ -54,6 +56,7 @@ const initialState = ImmutableMap({
   default_sensitive: false,
   resetFileKey: Math.floor((Math.random() * 0x10000)),
   idempotencyKey: null,
+  tagHistory: ImmutableList(),
 });
 
 function statusToTextMentions(state, status) {
@@ -87,7 +90,6 @@ function appendMedia(state, media) {
     map.update('media_attachments', list => list.push(media));
     map.set('is_uploading', false);
     map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
-    map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
     map.set('focusDate', new Date());
     map.set('idempotencyKey', uuid());
 
@@ -98,12 +100,10 @@ function appendMedia(state, media) {
 };
 
 function removeMedia(state, mediaId) {
-  const media    = state.get('media_attachments').find(item => item.get('id') === mediaId);
   const prevSize = state.get('media_attachments').size;
 
   return state.withMutations(map => {
     map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
-    map.update('text', text => text.replace(media.get('text_url'), '').trim());
     map.set('idempotencyKey', uuid());
 
     if (prevSize === 1) {
@@ -122,6 +122,18 @@ const insertSuggestion = (state, position, token, completion) => {
   });
 };
 
+const updateSuggestionTags = (state, token) => {
+  const prefix = token.slice(1);
+
+  return state.merge({
+    suggestions: state.get('tagHistory')
+      .filter(tag => tag.startsWith(prefix))
+      .slice(0, 4)
+      .map(tag => '#' + tag),
+    suggestion_token: token,
+  });
+};
+
 const insertEmoji = (state, position, emojiData) => {
   const emoji = emojiData.native;
 
@@ -159,10 +171,10 @@ export default function compose(state = initialState, action) {
   case STORE_HYDRATE:
     return hydrate(state, action.state.get('compose'));
   case COMPOSE_MOUNT:
-    return state.set('mounted', true);
+    return state.set('mounted', state.get('mounted') + 1);
   case COMPOSE_UNMOUNT:
     return state
-      .set('mounted', false)
+      .set('mounted', Math.max(state.get('mounted') - 1, 0))
       .set('is_composing', false);
   case COMPOSE_SENSITIVITY_CHANGE:
     return state.withMutations(map => {
@@ -252,6 +264,10 @@ export default function compose(state = initialState, action) {
     return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
   case COMPOSE_SUGGESTION_SELECT:
     return insertSuggestion(state, action.position, action.token, action.completion);
+  case COMPOSE_SUGGESTION_TAGS_UPDATE:
+    return updateSuggestionTags(state, action.token);
+  case COMPOSE_TAG_HISTORY_UPDATE:
+    return state.set('tagHistory', fromJS(action.tags));
   case TIMELINE_DELETE:
     if (action.id === state.get('in_reply_to')) {
       return state.set('in_reply_to', null);
@@ -265,7 +281,7 @@ export default function compose(state = initialState, action) {
       .set('is_submitting', false)
       .update('media_attachments', list => list.map(item => {
         if (item.get('id') === action.media.id) {
-          return item.set('description', action.media.description);
+          return fromJS(action.media);
         }
 
         return item;
diff --git a/app/javascript/mastodon/reducers/dropdown_menu.js b/app/javascript/mastodon/reducers/dropdown_menu.js
new file mode 100644
index 000000000..5449884cc
--- /dev/null
+++ b/app/javascript/mastodon/reducers/dropdown_menu.js
@@ -0,0 +1,18 @@
+import Immutable from 'immutable';
+import {
+  DROPDOWN_MENU_OPEN,
+  DROPDOWN_MENU_CLOSE,
+} from '../actions/dropdown_menu';
+
+const initialState = Immutable.Map({ openId: null, placement: null });
+
+export default function dropdownMenu(state = initialState, action) {
+  switch (action.type) {
+  case DROPDOWN_MENU_OPEN:
+    return state.merge({ openId: action.id, placement: action.placement });
+  case DROPDOWN_MENU_CLOSE:
+    return state.get('openId') === action.id ? state.set('openId', null) : state;
+  default:
+    return state;
+  }
+}
diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
index a028e989c..b84b2d18a 100644
--- a/app/javascript/mastodon/reducers/index.js
+++ b/app/javascript/mastodon/reducers/index.js
@@ -1,4 +1,5 @@
 import { combineReducers } from 'redux-immutable';
+import dropdown_menu from './dropdown_menu';
 import timelines from './timelines';
 import meta from './meta';
 import alerts from './alerts';
@@ -26,6 +27,7 @@ import lists from './lists';
 import listEditor from './list_editor';
 
 const reducers = {
+  dropdown_menu,
   timelines,
   meta,
   alerts,
diff --git a/app/javascript/mastodon/reducers/reports.js b/app/javascript/mastodon/reducers/reports.js
index a08bbec38..21ae6f93f 100644
--- a/app/javascript/mastodon/reducers/reports.js
+++ b/app/javascript/mastodon/reducers/reports.js
@@ -6,6 +6,7 @@ import {
   REPORT_CANCEL,
   REPORT_STATUS_TOGGLE,
   REPORT_COMMENT_CHANGE,
+  REPORT_FORWARD_CHANGE,
 } from '../actions/reports';
 import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
 
@@ -15,6 +16,7 @@ const initialState = ImmutableMap({
     account_id: null,
     status_ids: ImmutableSet(),
     comment: '',
+    forward: false,
   }),
 });
 
@@ -42,6 +44,8 @@ export default function reports(state = initialState, action) {
     });
   case REPORT_COMMENT_CHANGE:
     return state.setIn(['new', 'comment'], action.comment);
+  case REPORT_FORWARD_CHANGE:
+    return state.setIn(['new', 'forward'], action.forward);
   case REPORT_SUBMIT_REQUEST:
     return state.setIn(['new', 'isSubmitting'], true);
   case REPORT_SUBMIT_FAIL:
diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js
index 5120b2b67..5a47e7272 100644
--- a/app/javascript/mastodon/reducers/statuses.js
+++ b/app/javascript/mastodon/reducers/statuses.js
@@ -15,6 +15,8 @@ import {
   CONTEXT_FETCH_SUCCESS,
   STATUS_MUTE_SUCCESS,
   STATUS_UNMUTE_SUCCESS,
+  STATUS_REVEAL,
+  STATUS_HIDE,
 } from '../actions/statuses';
 import {
   TIMELINE_REFRESH_SUCCESS,
@@ -54,7 +56,7 @@ const normalizeStatus = (state, status) => {
     normalStatus.reblog = status.reblog.id;
   }
 
-  const searchContent = [status.spoiler_text, status.content].join('\n\n').replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
+  const searchContent = [status.spoiler_text, status.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
 
   const emojiMap = normalStatus.emojis.reduce((obj, emoji) => {
     obj[`:${emoji.shortcode}:`] = emoji;
@@ -62,8 +64,9 @@ const normalizeStatus = (state, status) => {
   }, {});
 
   normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
-  normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
-  normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''), emojiMap);
+  normalStatus.contentHtml  = emojify(normalStatus.content, emojiMap);
+  normalStatus.spoilerHtml  = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''), emojiMap);
+  normalStatus.hidden       = normalStatus.sensitive;
 
   return state.update(status.id, ImmutableMap(), map => map.mergeDeep(fromJS(normalStatus)));
 };
@@ -111,6 +114,14 @@ export default function statuses(state = initialState, action) {
     return state.setIn([action.id, 'muted'], true);
   case STATUS_UNMUTE_SUCCESS:
     return state.setIn([action.id, 'muted'], false);
+  case STATUS_REVEAL:
+    return state.withMutations(map => {
+      action.ids.forEach(id => map.setIn([id, 'hidden'], false));
+    });
+  case STATUS_HIDE:
+    return state.withMutations(map => {
+      action.ids.forEach(id => map.setIn([id, 'hidden'], true));
+    });
   case TIMELINE_REFRESH_SUCCESS:
   case TIMELINE_EXPAND_SUCCESS:
   case CONTEXT_FETCH_SUCCESS:
diff --git a/app/javascript/mastodon/settings.js b/app/javascript/mastodon/settings.js
index dbd969cb1..7643a508e 100644
--- a/app/javascript/mastodon/settings.js
+++ b/app/javascript/mastodon/settings.js
@@ -44,3 +44,4 @@ export default class Settings {
 }
 
 export const pushNotificationsSetting = new Settings('mastodon_push_notification_data');
+export const tagHistory = new Settings('mastodon_tag_history');
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index e45fc03d3..c484f074b 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -1,4 +1,183 @@
+$maximum-width: 1235px;
+$fluid-breakpoint: $maximum-width + 20px;
+$column-breakpoint: 700px;
+$small-breakpoint: 960px;
+
+.container {
+  box-sizing: border-box;
+  max-width: $maximum-width;
+  margin: 0 auto;
+  position: relative;
+
+  @media screen and (max-width: $fluid-breakpoint) {
+    width: 100%;
+    padding: 0 10px;
+  }
+}
+
 .landing-page {
+  .grid {
+    display: grid;
+    grid-gap: 10px;
+    grid-template-columns: 1fr 2fr;
+    grid-auto-columns: 25%;
+    grid-auto-rows: max-content;
+
+    .column-0 {
+      display: none;
+    }
+
+    .column-1 {
+      grid-column: 1;
+      grid-row: 1;
+    }
+
+    .column-2 {
+      grid-column: 2;
+      grid-row: 1;
+    }
+
+    .column-3 {
+      grid-column: 3;
+      grid-row: 1 / 3;
+    }
+
+    .column-4 {
+      grid-column: 1 / 3;
+      grid-row: 2;
+    }
+  }
+
+  @media screen and (max-width: $small-breakpoint) {
+    .grid {
+      grid-template-columns: 40% 60%;
+
+      .column-0 {
+        display: none;
+      }
+
+      .column-1 {
+        grid-column: 1;
+        grid-row: 1;
+
+        &.non-preview .landing-page__forms {
+          height: 100%;
+        }
+      }
+
+      .column-2 {
+        grid-column: 2;
+        grid-row: 1 / 3;
+
+        &.non-preview {
+          grid-column: 2;
+          grid-row: 1;
+        }
+      }
+
+      .column-3 {
+        grid-column: 1;
+        grid-row: 2 / 4;
+      }
+
+      .column-4 {
+        grid-column: 2;
+        grid-row: 3;
+
+        &.non-preview {
+          grid-column: 1 / 3;
+          grid-row: 2;
+        }
+      }
+    }
+  }
+
+  @media screen and (max-width: $column-breakpoint) {
+    .grid {
+      grid-template-columns: auto;
+
+      .column-0 {
+        display: block;
+        grid-column: 1;
+        grid-row: 1;
+      }
+
+      .column-1 {
+        grid-column: 1;
+        grid-row: 3;
+
+        .brand {
+          display: none;
+        }
+      }
+
+      .column-2 {
+        grid-column: 1;
+        grid-row: 2;
+
+        .landing-page__logo,
+        .landing-page__call-to-action {
+          display: none;
+        }
+
+        &.non-preview {
+          grid-column: 1;
+          grid-row: 2;
+        }
+      }
+
+      .column-3 {
+        grid-column: 1;
+        grid-row: 5;
+      }
+
+      .column-4 {
+        grid-column: 1;
+        grid-row: 4;
+
+        &.non-preview {
+          grid-column: 1;
+          grid-row: 4;
+        }
+      }
+    }
+  }
+
+  .column-flex {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .separator-or {
+    position: relative;
+    margin: 40px 0;
+    text-align: center;
+
+    &::before {
+      content: "";
+      display: block;
+      width: 100%;
+      height: 0;
+      border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
+      position: absolute;
+      top: 50%;
+      left: 0;
+    }
+
+    span {
+      display: inline-block;
+      background: $ui-base-color;
+      font-size: 12px;
+      font-weight: 500;
+      color: $ui-primary-color;
+      text-transform: uppercase;
+      position: relative;
+      z-index: 1;
+      padding: 0 8px;
+      cursor: default;
+    }
+  }
+
   p,
   li {
     font-family: 'mastodon-font-sans-serif', sans-serif;
@@ -15,6 +194,28 @@
     }
   }
 
+  .closed-registrations-message {
+    margin-top: 20px;
+
+    &,
+    p {
+      text-align: center;
+      font-size: 12px;
+      line-height: 18px;
+      color: $ui-primary-color;
+      margin-bottom: 0;
+
+      a {
+        color: $ui-highlight-color;
+        text-decoration: underline;
+      }
+    }
+
+    p:last-child {
+      margin-bottom: 0;
+    }
+  }
+
   em {
     display: inline;
     margin: 0;
@@ -116,10 +317,14 @@
   }
 
   hr {
-    border-color: rgba($ui-base-lighter-color, .6);
+    width: 100%;
+    height: 0;
+    border: 0;
+    border-bottom: 1px solid rgba($ui-base-lighter-color, .6);
+    margin: 20px 0;
   }
 
-  .container {
+  .container-alt {
     width: 100%;
     box-sizing: border-box;
     max-width: 800px;
@@ -152,24 +357,20 @@
         }
       }
     }
+  }
 
-    .mascot-container {
-      max-width: 800px;
-      margin: 0 auto;
-      position: absolute;
-      top: 0;
-      left: 0;
-      right: 0;
-      height: 100%;
+  .brand {
+    a {
+      padding-left: 0;
+      padding-right: 0;
+      color: $white;
     }
 
-    .mascot {
-      position: absolute;
-      bottom: -14px;
-      width: auto;
-      height: auto;
-      left: 60px;
-      z-index: 3;
+    img {
+      height: 32px;
+      position: relative;
+      top: 4px;
+      left: -10px;
     }
   }
 
@@ -177,7 +378,7 @@
     line-height: 30px;
     overflow: hidden;
 
-    .container {
+    .container-alt {
       display: flex;
       justify-content: space-between;
     }
@@ -203,21 +404,6 @@
         }
       }
 
-      .brand {
-        a {
-          padding-left: 0;
-          padding-right: 0;
-          color: $white;
-        }
-
-        img {
-          height: 32px;
-          position: relative;
-          top: 4px;
-          left: -10px;
-        }
-      }
-
       ul {
         list-style: none;
         margin: 0;
@@ -243,53 +429,6 @@
       align-items: center;
       position: relative;
 
-      .floats {
-        position: absolute;
-        width: 100%;
-        height: 100%;
-        top: 0;
-        left: 0;
-
-        div {
-          position: absolute;
-          transition: all 0.1s linear;
-          animation-name: floating;
-          animation-iteration-count: infinite;
-          animation-direction: alternate;
-          animation-timing-function: ease-in-out;
-          z-index: 2;
-        }
-
-        .float-1 {
-          width: 324px;
-          height: 170px;
-          right: -120px;
-          bottom: 0;
-          animation-duration: 3s;
-          background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 447.1875 234.375" height="170" width="324"><path fill="#{hex-color($ui-base-lighter-color)}" d="M21.69 233.366c-6.45-1.268-13.347-5.63-16.704-10.564-10.705-15.734-1.513-37.724 18.632-44.57l4.8-1.632.173-17.753c.146-14.77.515-19.063 2.2-25.55 6.736-25.944 24.46-46.032 47.766-54.137 11.913-4.143 19.558-5.366 34.178-5.47l13.828-.096V71.12c0-4.755 2.853-17.457 5.238-23.327 8.588-21.137 26.735-35.957 52.153-42.593 23.248-6.07 50.153-6.415 71.863-.923 11.14 2.82 25.686 9.957 33.857 16.615 19.335 15.756 31.82 41.05 35.183 71.275.59 5.305.672 5.435 3.11 4.926 11.833-2.474 30.4-3.132 40.065-1.42 24.388 4.32 40.568 19.076 47.214 43.058 2.16 7.8 3.953 23.894 3.59 32.237l-.24 5.498 5.156 1.317c6.392 1.633 14.55 7.098 18.003 12.062 1.435 2.062 3.305 6.597 4.156 10.078 1.428 5.84 1.43 6.8.04 12.44-1.807 7.318-5.672 13.252-10.872 16.694-8.508 5.63 3.756 5.33-211.916 5.216-108.56-.056-199.22-.464-201.47-.906z"/></svg>');
-        }
-
-        .float-2 {
-          width: 241px;
-          height: 100px;
-          right: 210px;
-          bottom: 0;
-          animation-duration: 3.5s;
-          animation-delay: 0.2s;
-          background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 536.25 222.1875" height="100" width="241"><path fill="#{hex-color($ui-base-lighter-color)}" d="M42.626 221.23c-14.104-1.174-26.442-5.133-32.825-10.534-4.194-3.548-7.684-10.66-8.868-18.075-1.934-12.102.633-22.265 7.528-29.81 7.61-8.328 19.998-12.76 39.855-14.257l8.47-.638-2.08-6.223c-4.826-14.422-6.357-24.813-6.37-43.255-.012-14.923.28-18.513 2.1-25.724 2.283-9.048 8.483-23.034 13.345-30.1 14.76-21.45 43.505-38.425 70.535-41.65 30.628-3.655 64.47 12.073 89.668 41.673l5.955 6.995 2.765-4.174c1.52-2.296 5.74-6.93 9.376-10.295 18.382-17.02 43.436-20.676 73.352-10.705 12.158 4.052 21.315 9.53 29.64 17.733 12.752 12.562 18.16 25.718 18.19 44.26l.02 10.98 2.312-3.01c15.64-20.365 42.29-20.485 62.438-.28 3.644 3.653 7.558 8.593 8.697 10.976 4.895 10.24 5.932 25.688 2.486 37.046-.76 2.507-1.388 4.816-1.393 5.13-.006.316 6.845.87 15.224 1.234 53.06 2.297 76.356 12.98 81.817 37.526 3.554 15.973-3.71 28.604-19.566 34.02-4.554 1.555-17.922 1.655-234.517 1.757-126.327.06-233.497-.21-238.154-.597z"/></svg>');
-        }
-
-        .float-3 {
-          width: 267px;
-          height: 140px;
-          right: 110px;
-          top: -30px;
-          animation-duration: 4s;
-          animation-delay: 0.5s;
-          background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 388.125 202.5" height="140" width="267"><path fill="#{hex-color($ui-base-lighter-color)}" d="M181.37 201.458c-17.184-1.81-36.762-8.944-49.523-18.05l-5.774-4.12-8.074 2.63c-11.468 3.738-21.382 4.962-35.815 4.422-14.79-.554-24.577-2.845-36.716-8.594-15.483-7.332-28.498-19.98-35.985-34.968C2.44 128.675-.94 108.435.9 91.356c3.362-31.234 18.197-53.698 43.63-66.074 12.803-6.23 22.384-8.55 37.655-9.122 14.433-.54 24.347.684 35.814 4.42l8.073 2.633 5.635-4.01c24.81-17.656 60.007-23.332 92.914-14.985 10.11 2.565 25.498 9.62 33.102 15.178l5.068 3.704 7.632-2.564c10.89-3.66 21.086-4.916 35.516-4.376 45.816 1.716 76.422 30.03 81.285 75.196 1.84 17.08-1.54 37.32-8.585 51.422-7.487 14.99-20.502 27.636-35.984 34.968-12.14 5.75-21.926 8.04-36.716 8.593-14.43.54-24.626-.716-35.516-4.376l-7.632-2.564-5.068 3.704c-12.844 9.387-32.714 16.488-51.545 18.42-10.607 1.09-13.916 1.08-24.81-.066z"/></svg>');
-        }
-      }
-
       .heading {
         position: relative;
         z-index: 4;
@@ -346,18 +485,18 @@
     background: darken($ui-base-color, 4%);
     padding: 20px 0;
 
-    .container {
+    .container-alt {
       position: relative;
       padding-right: 280px + 15px;
     }
 
-    .information-board-sections {
+    &__sections {
       display: flex;
       justify-content: space-between;
       flex-wrap: wrap;
     }
 
-    .section {
+    &__section {
       flex: 1 0 0;
       font-family: 'mastodon-font-sans-serif', sans-serif;
       font-size: 16px;
@@ -382,6 +521,10 @@
         font-size: 32px;
         line-height: 48px;
       }
+
+      @media screen and (max-width: $column-breakpoint) {
+        text-align: center;
+      }
     }
 
     .panel {
@@ -460,114 +603,310 @@
     }
   }
 
-  .features {
-    padding: 50px 0;
+  &.alternative {
+    padding: 10px 0;
 
-    .container {
-      display: flex;
-    }
+    .brand {
+      text-align: center;
+      padding: 30px 0;
+      margin-bottom: 10px;
 
-    #mastodon-timeline {
-      display: flex;
-      -webkit-overflow-scrolling: touch;
-      -ms-overflow-style: -ms-autohiding-scrollbar;
-      font-family: 'mastodon-font-sans-serif', sans-serif;
-      font-size: 13px;
-      line-height: 18px;
-      font-weight: 400;
-      color: $primary-text-color;
-      width: 330px;
-      margin-right: 30px;
-      flex: 0 0 auto;
-      background: $ui-base-color;
-      overflow: hidden;
-      border-radius: 4px;
-      box-shadow: 0 0 6px rgba($black, 0.1);
+      img {
+        position: static;
+        padding: 10px 0;
+      }
 
-      .column-header {
-        color: inherit;
-        font-family: inherit;
-        font-size: 16px;
-        line-height: inherit;
-        font-weight: inherit;
-        margin: 0;
-        padding: 15px;
+      @media screen and (max-width: $small-breakpoint) {
+        padding: 15px 0;
       }
 
-      .column {
+      @media screen and (max-width: $column-breakpoint) {
         padding: 0;
-        border-radius: 4px;
-        overflow: hidden;
+        margin-bottom: -10px;
       }
+    }
+  }
+
+  &__information,
+  &__forms {
+    padding: 20px;
+  }
+
+  &__call-to-action {
+    background: darken($ui-base-color, 4%);
+    border-radius: 4px;
+    padding: 25px 40px;
+    overflow: hidden;
 
-      .scrollable {
-        height: 400px;
+    .row {
+      display: flex;
+      flex-direction: row-reverse;
+      flex-wrap: wrap;
+      justify-content: space-between;
+      align-items: center;
+    }
+
+    .row__information-board {
+      display: flex;
+      justify-content: flex-end;
+      align-items: flex-end;
+
+      .information-board__section {
+        flex: 1 0 auto;
+        padding: 0 10px;
       }
+    }
+
+    .row__mascot {
+      flex: 1;
+      margin: 10px -50px 0 0;
+    }
+  }
+
+  &__logo {
+    margin-right: 20px;
+
+    img {
+      height: 50px;
+      width: auto;
+      mix-blend-mode: lighten;
+    }
+  }
+
+  &__information {
+    padding: 45px 40px;
+    margin-bottom: 10px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    @media screen and (max-width: $column-breakpoint) {
+      padding: 25px 20px;
+    }
+  }
+
+  &__information,
+  &__forms,
+  #mastodon-timeline {
+    box-sizing: border-box;
+    background: $ui-base-color;
+    border-radius: 4px;
+    box-shadow: 0 0 6px rgba($black, 0.1);
+  }
+
+  &__mascot {
+    height: 104px;
+    position: relative;
+    left: -40px;
+    bottom: 25px;
+
+    img {
+      height: 190px;
+      width: auto;
+    }
+  }
 
-      p {
-        font-size: inherit;
-        line-height: inherit;
-        font-weight: inherit;
-        color: $primary-text-color;
+  &__short-description {
+    .row {
+      display: flex;
+      flex-wrap: wrap;
+      align-items: center;
+      margin-bottom: 40px;
+    }
+
+    @media screen and (max-width: $column-breakpoint) {
+      .row {
         margin-bottom: 20px;
+      }
+    }
 
-        &:last-child {
-          margin-bottom: 0;
-        }
+    p a {
+      color: $ui-secondary-color;
+    }
 
-        a {
+    h1 {
+      font-weight: 500;
+      color: $primary-text-color;
+      margin-bottom: 0;
+
+      small {
+        color: $ui-primary-color;
+
+        span {
           color: $ui-secondary-color;
-          text-decoration: none;
         }
       }
     }
 
-    .about-mastodon {
-      max-width: 675px;
+    p:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  &__hero {
+    margin-bottom: 10px;
 
-      p {
-        margin-bottom: 20px;
+    img {
+      display: block;
+      margin: 0;
+      max-width: 100%;
+      height: auto;
+      border-radius: 4px;
+    }
+  }
+
+  &__forms {
+    height: 100%;
+
+    @media screen and (max-width: $small-breakpoint) {
+      height: auto;
+    }
+
+    @media screen and (max-width: $column-breakpoint) {
+      background: transparent;
+      box-shadow: none;
+      padding: 0 20px;
+      margin-top: 30px;
+      margin-bottom: 40px;
+
+      .separator-or {
+        span {
+          background: darken($ui-base-color, 8%);
+        }
       }
+    }
 
-      .features-list {
-        margin-top: 20px;
+    hr {
+      margin: 40px 0;
+    }
 
-        .features-list__row {
-          display: flex;
-          padding: 10px 0;
-          justify-content: space-between;
+    .button {
+      display: block;
+    }
 
-          &:first-child {
-            padding-top: 0;
-          }
+    .subtle-hint a {
+      text-decoration: none;
 
-          .visual {
-            flex: 0 0 auto;
-            display: flex;
-            align-items: center;
-            margin-left: 15px;
+      &:hover,
+      &:focus,
+      &:active {
+        text-decoration: underline;
+      }
+    }
+  }
 
-            .fa {
-              display: block;
-              color: $ui-primary-color;
-              font-size: 48px;
-            }
-          }
+  #mastodon-timeline {
+    display: flex;
+    -webkit-overflow-scrolling: touch;
+    -ms-overflow-style: -ms-autohiding-scrollbar;
+    font-family: 'mastodon-font-sans-serif', sans-serif;
+    font-size: 13px;
+    line-height: 18px;
+    font-weight: 400;
+    color: $primary-text-color;
+    width: 100%;
+    flex: 1 1 auto;
+    overflow: hidden;
+    height: 100%;
 
-          .text {
-            font-size: 16px;
-            line-height: 30px;
-            color: $ui-primary-color;
+    .column-header {
+      color: inherit;
+      font-family: inherit;
+      font-size: 16px;
+      line-height: inherit;
+      font-weight: inherit;
+      margin: 0;
+      padding: 0;
+    }
 
-            h6 {
-              font-size: inherit;
-              line-height: inherit;
-              margin-bottom: 0;
-            }
-          }
+    .column {
+      padding: 0;
+      border-radius: 4px;
+      overflow: hidden;
+      width: 100%;
+    }
+
+    .scrollable {
+      height: 400px;
+    }
+
+    p {
+      font-size: inherit;
+      line-height: inherit;
+      font-weight: inherit;
+      color: $primary-text-color;
+      margin-bottom: 20px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      a {
+        color: $ui-secondary-color;
+        text-decoration: none;
+      }
+    }
+
+    @media screen and (max-width: $column-breakpoint) {
+      height: 90vh;
+    }
+  }
+
+  &__features {
+    & > p {
+      padding-right: 60px;
+    }
+
+    .features-list {
+      margin: 40px 0;
+      margin-top: 30px;
+    }
+
+    &__action {
+      text-align: center;
+    }
+  }
+
+  .features-list {
+    .features-list__row {
+      display: flex;
+      padding: 10px 0;
+      justify-content: space-between;
+
+      .visual {
+        flex: 0 0 auto;
+        display: flex;
+        align-items: center;
+        margin-left: 15px;
+
+        .fa {
+          display: block;
+          color: $ui-primary-color;
+          font-size: 48px;
+        }
+      }
+
+      .text {
+        font-size: 16px;
+        line-height: 30px;
+        color: $ui-primary-color;
+
+        h6 {
+          font-size: inherit;
+          line-height: inherit;
+          margin-bottom: 0;
         }
       }
     }
+
+    @media screen and (min-width: $small-breakpoint) {
+      display: grid;
+      grid-gap: 30px;
+      grid-template-columns: 1fr 1fr;
+      grid-auto-columns: 50%;
+      grid-auto-rows: max-content;
+    }
   }
 
   .extended-description {
@@ -600,21 +939,31 @@
     }
   }
 
+  &__footer {
+    margin-top: 10px;
+    text-align: center;
+    color: $ui-base-lighter-color;
+
+    p {
+      font-size: 14px;
+
+      a {
+        color: inherit;
+        text-decoration: underline;
+      }
+    }
+  }
+
   @media screen and (max-width: 840px) {
-    .container {
+    .container-alt {
       padding: 0 20px;
     }
 
     .information-board {
-
-      .container {
+      .container-alt {
         padding-right: 20px;
       }
 
-      .section {
-        text-align: center;
-      }
-
       .panel {
         position: static;
         margin-top: 20px;
@@ -626,16 +975,6 @@
         }
       }
     }
-
-    .header-wrapper .mascot {
-      left: 20px;
-    }
-  }
-
-  @media screen and (max-width: 689px) {
-    .header-wrapper .mascot {
-      display: none;
-    }
   }
 
   @media screen and (max-width: 675px) {
@@ -651,13 +990,12 @@
       }
     }
 
-    .header .container,
-    .features .container {
+    .header .container-alt,
+    .features .container-alt {
       display: block;
     }
 
     .header {
-
       .links {
         padding-top: 15px;
         background: darken($ui-base-color, 4%);
@@ -682,10 +1020,6 @@
         margin-top: 30px;
         padding: 0;
 
-        .floats {
-          display: none;
-        }
-
         .heading {
           padding: 30px 20px;
           text-align: center;
@@ -700,16 +1034,6 @@
         }
       }
     }
-
-    .features #mastodon-timeline {
-      height: 70vh;
-      width: 100%;
-      margin-bottom: 50px;
-
-      .column {
-        width: 100%;
-      }
-    }
   }
 
   .cta {
@@ -717,108 +1041,55 @@
   }
 
   &.tag-page {
-    .features {
-      padding: 30px 0;
-
-      .container {
-        max-width: 820px;
-
-        #mastodon-timeline {
-          margin-right: 0;
-          border-top-right-radius: 0;
-        }
-
-        .about-mastodon {
-          .about-hashtag {
-            background: darken($ui-base-color, 4%);
-            padding: 0 20px 20px 30px;
-            border-radius: 0 5px 5px 0;
-
-            .brand {
-              padding-top: 20px;
-              margin-bottom: 20px;
-
-              img {
-                height: 48px;
-                width: auto;
-              }
-            }
-
-            p {
-              strong {
-                color: $ui-secondary-color;
-                font-weight: 700;
-              }
-            }
-
-            .cta {
-              margin: 0;
-
-              .button {
-                margin-right: 4px;
-              }
-            }
-          }
+    .grid {
+      @media screen and (min-width: $small-breakpoint) {
+        grid-template-columns: 33% 67%;
+      }
 
-          .features-list {
-            margin-left: 30px;
-            margin-right: 10px;
-          }
-        }
+      .column-2 {
+        grid-column: 2;
+        grid-row: 1;
       }
     }
 
-    @media screen and (max-width: 675px) {
-      .features {
-        padding: 10px 0;
+    .brand {
+      text-align: unset;
+      padding: 0;
 
-        .container {
-          display: flex;
-          flex-direction: column;
-
-          #mastodon-timeline {
-            order: 2;
-            flex: 0 0 auto;
-            height: 60vh;
-            margin-bottom: 20px;
-            border-top-right-radius: 4px;
-          }
+      img {
+        height: 48px;
+        width: auto;
+      }
+    }
 
-          .about-mastodon {
-            order: 1;
-            flex: 0 0 auto;
-            max-width: 100%;
+    .cta {
+      margin: 0;
 
-            .about-hashtag {
-              background: unset;
-              padding: 0;
-              border-radius: 0;
+      .button {
+        margin-right: 4px;
+      }
+    }
 
-              .cta {
-                margin: 20px 0;
-              }
-            }
+    @media screen and (max-width: $column-breakpoint) {
+      .grid {
+        .column-1 {
+          grid-column: 1;
+          grid-row: 2;
+        }
 
-            .features-list {
-              display: none;
-            }
-          }
+        .column-2 {
+          grid-column: 1;
+          grid-row: 1;
         }
       }
-    }
-  }
-}
 
-@keyframes floating {
-  from {
-    transform: translate(0, 0);
-  }
-
-  65% {
-    transform: translate(0, 4px);
-  }
+      .brand {
+        margin: 0;
+      }
 
-  to {
-    transform: translate(0, -0);
+      .landing-page__features {
+        display: none;
+      }
+    }
   }
 }
diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss
index 9015d04cb..873963c90 100644
--- a/app/javascript/styles/mastodon/accounts.scss
+++ b/app/javascript/styles/mastodon/accounts.scss
@@ -97,32 +97,6 @@
     }
   }
 
-  .controls {
-    position: absolute;
-    top: 15px;
-    left: 15px;
-    z-index: 2;
-
-    .icon-button {
-      color: rgba($white, 0.8);
-      text-decoration: none;
-      font-size: 13px;
-      line-height: 13px;
-      font-weight: 500;
-
-      .fa {
-        font-weight: 400;
-        margin-right: 5px;
-      }
-
-      &:hover,
-      &:active,
-      &:focus {
-        color: $white;
-      }
-    }
-  }
-
   .roles {
     margin-bottom: 30px;
     padding: 0 15px;
@@ -226,6 +200,40 @@
   }
 }
 
+.card,
+.account-grid-card {
+  .controls {
+    position: absolute;
+    top: 15px;
+    left: 15px;
+    z-index: 2;
+
+    .icon-button {
+      color: rgba($white, 0.8);
+      text-decoration: none;
+      font-size: 13px;
+      line-height: 13px;
+      font-weight: 500;
+
+      .fa {
+        font-weight: 400;
+        margin-right: 5px;
+      }
+
+      &:hover,
+      &:active,
+      &:focus {
+        color: $white;
+      }
+    }
+  }
+}
+
+.account-grid-card .controls {
+  left: auto;
+  right: 15px;
+}
+
 .pagination {
   padding: 30px 0;
   text-align: center;
@@ -233,8 +241,8 @@
 
   a,
   .current,
-  .next,
-  .prev,
+  .newer,
+  .older,
   .page,
   .gap {
     font-size: 14px;
@@ -257,13 +265,13 @@
     cursor: default;
   }
 
-  .prev,
-  .next {
+  .older,
+  .newer {
     text-transform: uppercase;
     color: $ui-secondary-color;
   }
 
-  .prev {
+  .older {
     float: left;
     padding-left: 0;
 
@@ -273,7 +281,7 @@
     }
   }
 
-  .next {
+  .newer {
     float: right;
     padding-right: 0;
 
@@ -295,8 +303,8 @@
       display: none;
     }
 
-    .next,
-    .prev {
+    .newer,
+    .older {
       display: inline-block;
     }
   }
@@ -411,13 +419,14 @@
       font-weight: 400;
     }
 
-    .note {
+    .account__header__content {
       padding: 10px 15px;
       padding-top: 15px;
-      box-sizing: border-box;
       color: lighten($ui-base-color, 26%);
       word-wrap: break-word;
-      min-height: 80px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      height: 5.5em;
     }
   }
 }
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 0c343e1df..e6bd0c717 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -105,6 +105,16 @@
       margin-bottom: 30px;
     }
 
+    h4 {
+      text-transform: uppercase;
+      font-size: 13px;
+      font-weight: 500;
+      color: $ui-primary-color;
+      padding-bottom: 8px;
+      margin-bottom: 8px;
+      border-bottom: 1px solid lighten($ui-base-color, 8%);
+    }
+
     h6 {
       font-size: 16px;
       color: $ui-secondary-color;
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index b5d77ff63..bec0d4d91 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -47,6 +47,10 @@ body {
     padding-bottom: 0;
   }
 
+  &.player {
+    text-align: center;
+  }
+
   &.embed {
     background: transparent;
     margin: 0;
@@ -118,5 +122,6 @@ button {
     height: 100%;
     align-items: center;
     justify-content: center;
+    outline: 0 !important;
   }
 }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 75e37237b..cd0dbbab8 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -40,14 +40,20 @@
     cursor: default;
   }
 
-  &.button-alternative {
+  &.button-primary,
+  &.button-alternative,
+  &.button-secondary,
+  &.button-alternative-2 {
     font-size: 16px;
     line-height: 36px;
     height: auto;
-    color: $ui-base-color;
-    background: $ui-primary-color;
     text-transform: none;
     padding: 4px 16px;
+  }
+
+  &.button-alternative {
+    color: $ui-base-color;
+    background: $ui-primary-color;
 
     &:active,
     &:focus,
@@ -56,15 +62,20 @@
     }
   }
 
+  &.button-alternative-2 {
+    background: $ui-base-lighter-color;
+
+    &:active,
+    &:focus,
+    &:hover {
+      background-color: lighten($ui-base-lighter-color, 4%);
+    }
+  }
+
   &.button-secondary {
-    font-size: 16px;
-    line-height: 36px;
-    height: auto;
     color: $ui-primary-color;
-    text-transform: none;
     background: transparent;
     padding: 3px 15px;
-    border-radius: 4px;
     border: 1px solid $ui-primary-color;
 
     &:active,
@@ -433,6 +444,34 @@
       min-width: 40%;
       margin: 5px;
 
+      &__actions {
+        background: linear-gradient(180deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
+        display: flex;
+        align-items: flex-start;
+        justify-content: space-between;
+        opacity: 0;
+        transition: opacity .1s ease;
+
+        .icon-button {
+          flex: 0 1 auto;
+          color: $ui-secondary-color;
+          font-size: 14px;
+          font-weight: 500;
+          padding: 10px;
+          font-family: inherit;
+
+          &:hover,
+          &:focus,
+          &:active {
+            color: lighten($ui-secondary-color, 4%);
+          }
+        }
+
+        &.active {
+          opacity: 1;
+        }
+      }
+
       &-description {
         position: absolute;
         z-index: 2;
@@ -470,10 +509,6 @@
           opacity: 1;
         }
       }
-
-      .icon-button {
-        mix-blend-mode: difference;
-      }
     }
 
     .compose-form__upload-thumbnail {
@@ -481,8 +516,9 @@
       background-position: center;
       background-size: cover;
       background-repeat: no-repeat;
-      height: 100px;
+      height: 140px;
       width: 100%;
+      overflow: hidden;
     }
   }
 
@@ -686,12 +722,13 @@
   background: transparent;
   border: 0;
   color: lighten($ui-base-color, 8%);
-  font-weight: 500;
+  font-weight: 700;
   font-size: 11px;
   padding: 0 6px;
   text-transform: uppercase;
-  line-height: inherit;
+  line-height: 20px;
   cursor: pointer;
+  vertical-align: middle;
 }
 
 .status__prepend-icon-wrapper {
@@ -825,12 +862,27 @@
   border-bottom: 1px solid $ui-secondary-color;
   display: flex;
 
-  .status__content {
-    flex: 1 1 auto;
-    padding: 10px;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
+  .status-check-box__status {
+    margin: 10px 0 10px 10px;
+    flex: 1;
+
+    .media-gallery {
+      max-width: 250px;
+    }
+
+    .status__content {
+      padding: 0;
+      white-space: normal;
+    }
+
+    .video-player {
+      margin-top: 8px;
+      max-width: 250px;
+    }
+
+    .media-gallery__item-thumbnail {
+      cursor: default;
+    }
   }
 }
 
@@ -899,6 +951,11 @@
       height: 24px;
       margin: -1px 0 0;
     }
+
+    .status__content__spoiler-link {
+      line-height: 24px;
+      margin: -1px 0 0;
+    }
   }
 
   .video-player {
@@ -1376,36 +1433,29 @@
 
 .image-loader {
   position: relative;
+  width: 100%;
+  height: 100%;
 
   &.image-loader--loading {
+    display: flex;
+    align-content: center;
+
     .image-loader__preview-canvas {
       filter: blur(2px);
     }
   }
 
-  .image-loader__img {
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    max-width: 100%;
-    max-height: 100%;
-    background-image: none;
+  &.image-loader--amorphous .image-loader__preview-canvas {
+    display: none;
   }
+}
 
-  &.image-loader--amorphous {
-    position: static;
-
-    .image-loader__preview-canvas {
-      display: none;
-    }
-
-    .image-loader__img {
-      position: static;
-      width: auto;
-      height: auto;
-    }
-  }
+.zoomable-image {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-content: center;
 }
 
 .navigation-bar {
@@ -1766,7 +1816,7 @@
   position: absolute;
   top: 0;
   left: 0;
-  background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
+  background: lighten($ui-base-color, 13%);
   box-sizing: border-box;
   padding: 0;
   display: flex;
@@ -1779,10 +1829,19 @@
   &.darker {
     background: $ui-base-color;
   }
+}
 
-  > .mastodon {
-    background: url('~images/elephant_ui_plane.svg') no-repeat left bottom / contain;
-    flex: 1;
+.drawer__inner__mastodon {
+  background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
+  flex: 1;
+  min-height: 47px;
+
+  > img {
+    display: block;
+    object-fit: contain;
+    object-position: bottom left;
+    width: 100%;
+    height: 100%;
   }
 }
 
@@ -1827,7 +1886,7 @@
   font-size: 14px;
   font-weight: 500;
   border-bottom: 2px solid lighten($ui-base-color, 8%);
-  transition: all 200ms linear;
+  transition: all 50ms linear;
 
   .fa {
     font-weight: 400;
@@ -1844,7 +1903,6 @@
   &:active {
     @media screen and (min-width: 631px) {
       background: lighten($ui-base-color, 14%);
-      transition: all 100ms linear;
     }
   }
 
@@ -1913,7 +1971,7 @@
   font-family: inherit;
   color: $ui-highlight-color;
   cursor: pointer;
-  flex: 0 0 auto;
+  white-space: nowrap;
   font-size: 16px;
   padding: 0 5px 0 0;
   z-index: 3;
@@ -2070,6 +2128,17 @@
   margin-right: 5px;
 }
 
+.column-link__badge {
+  display: inline-block;
+  border-radius: 4px;
+  font-size: 12px;
+  line-height: 19px;
+  font-weight: 500;
+  background: $ui-base-color;
+  padding: 4px 8px;
+  margin: -6px 10px;
+}
+
 .column-subheading {
   background: $ui-base-color;
   color: $ui-base-lighter-color;
@@ -2188,7 +2257,6 @@
 
 .status-card {
   display: flex;
-  cursor: pointer;
   font-size: 14px;
   border: 1px solid lighten($ui-base-color, 8%);
   border-radius: 4px;
@@ -2197,20 +2265,58 @@
   text-decoration: none;
   overflow: hidden;
 
-  &:hover {
-    background: lighten($ui-base-color, 8%);
+  &__actions {
+    bottom: 0;
+    left: 0;
+    position: absolute;
+    right: 0;
+    top: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    & > div {
+      background: rgba($base-shadow-color, 0.6);
+      border-radius: 4px;
+      padding: 12px 9px;
+      flex: 0 0 auto;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+
+    button,
+    a {
+      display: inline;
+      color: $primary-text-color;
+      background: transparent;
+      border: 0;
+      padding: 0 5px;
+      text-decoration: none;
+      opacity: 0.6;
+      font-size: 18px;
+      line-height: 18px;
+
+      &:hover,
+      &:active,
+      &:focus {
+        opacity: 1;
+      }
+    }
+
+    a {
+      font-size: 19px;
+      position: relative;
+      bottom: -1px;
+    }
   }
 }
 
-.status-card-video,
-.status-card-rich,
-.status-card-photo {
-  margin-top: 14px;
-  overflow: hidden;
+a.status-card {
+  cursor: pointer;
 
-  iframe {
-    width: 100%;
-    height: auto;
+  &:hover {
+    background: lighten($ui-base-color, 8%);
   }
 }
 
@@ -2238,6 +2344,7 @@
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
+  text-decoration: none;
 }
 
 .status-card__content {
@@ -2259,6 +2366,7 @@
 .status-card__image {
   flex: 0 0 100px;
   background: lighten($ui-base-color, 8%);
+  position: relative;
 }
 
 .status-card.horizontal {
@@ -2284,6 +2392,8 @@
   width: 100%;
   height: 100%;
   object-fit: cover;
+  background-size: cover;
+  background-position: center center;
 }
 
 .load-more {
@@ -2382,7 +2492,6 @@
 
 .column-header {
   display: flex;
-  padding: 15px;
   font-size: 16px;
   background: lighten($ui-base-color, 4%);
   flex: 0 0 auto;
@@ -2393,15 +2502,21 @@
   overflow: hidden;
 
   & > button {
-    display: flex;
-    flex: auto;
     margin: 0;
     border: none;
-    padding: 0;
+    padding: 15px 0 15px 15px;
     color: inherit;
     background: transparent;
     font: inherit;
     text-align: left;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    white-space: nowrap;
+    flex: 1;
+  }
+
+  & > .column-header__back-button {
+    color: $ui-highlight-color;
   }
 
   &.active {
@@ -2422,8 +2537,6 @@
 .column-header__buttons {
   height: 48px;
   display: flex;
-  margin: -15px;
-  margin-left: 0;
 }
 
 .column-header__links .text-btn {
@@ -2503,14 +2616,6 @@
   }
 }
 
-.column-header__title {
-  display: inline-block;
-  text-overflow: ellipsis;
-  overflow: hidden;
-  white-space: nowrap;
-  flex: 1;
-}
-
 .text-btn {
   display: inline-block;
   padding: 0;
@@ -2615,12 +2720,16 @@
   background: $base-overlay-background;
   color: $ui-primary-color;
   border: 0;
+  padding: 0;
   width: 100%;
   height: 100%;
+  border-radius: 4px;
+  appearance: none;
 
   &:hover,
   &:active,
   &:focus {
+    padding: 0;
     color: lighten($ui-primary-color, 8%);
   }
 }
@@ -2633,7 +2742,7 @@
 .media-spoiler__trigger {
   display: block;
   font-size: 11px;
-  font-weight: 500;
+  font-weight: 700;
 }
 
 .spoiler-button {
@@ -2687,33 +2796,26 @@
   }
 }
 
-.modal-container__nav {
-  align-items: center;
-  background: rgba($base-overlay-background, 0.5);
-  box-sizing: border-box;
-  border: 0;
+.account--follows-info {
   color: $primary-text-color;
-  cursor: pointer;
-  display: flex;
-  font-size: 24px;
-  height: 100%;
-  padding: 30px 15px;
   position: absolute;
-  top: 0;
-}
-
-.modal-container__nav--left {
-  left: -61px;
-}
-
-.modal-container__nav--right {
-  right: -61px;
+  top: 10px;
+  left: 10px;
+  opacity: 0.7;
+  display: inline-block;
+  vertical-align: top;
+  background-color: rgba($base-overlay-background, 0.4);
+  text-transform: uppercase;
+  font-size: 11px;
+  font-weight: 500;
+  padding: 4px;
+  border-radius: 4px;
 }
 
-.account--follows-info {
+.account--muting-info {
   color: $primary-text-color;
   position: absolute;
-  top: 10px;
+  top: 40px;
   left: 10px;
   opacity: 0.7;
   display: inline-block;
@@ -3218,6 +3320,43 @@
   font-weight: 500;
 }
 
+.search-results__section {
+  margin-bottom: 20px;
+
+  h5 {
+    position: relative;
+
+    &::before {
+      content: "";
+      display: block;
+      position: absolute;
+      left: 0;
+      right: 0;
+      top: 50%;
+      width: 100%;
+      height: 0;
+      border-top: 1px solid lighten($ui-base-color, 8%);
+    }
+
+    span {
+      display: inline-block;
+      background: $ui-base-color;
+      color: $ui-primary-color;
+      font-size: 14px;
+      font-weight: 500;
+      padding: 10px;
+      position: relative;
+      z-index: 1;
+      cursor: default;
+    }
+  }
+
+  .account:last-child,
+  & > div:last-child .status {
+    border-bottom: 0;
+  }
+}
+
 .search-results__hashtag {
   display: block;
   padding: 10px;
@@ -3269,29 +3408,27 @@
   z-index: 9999;
 }
 
+.video-modal {
+  max-width: 100vw;
+  max-height: 100vh;
+  position: relative;
+}
+
 .media-modal {
-  max-width: 80vw;
-  max-height: 80vh;
+  width: 100%;
+  height: 100%;
   position: relative;
 
-  .extended-video-player,
   img,
   canvas,
   video {
-    max-width: 80vw;
-    max-height: 80vh;
+    max-width: 100vw;
+    max-height: 100vh;
     width: auto;
     height: auto;
     margin: auto;
   }
 
-  .extended-video-player,
-  video {
-    display: flex;
-    width: 80vw;
-    height: 80vh;
-  }
-
   img,
   canvas {
     display: block;
@@ -3300,12 +3437,65 @@
   }
 
   .react-swipeable-view-container {
-    max-width: 80vw;
+    width: 100vw;
+    height: 100%;
   }
 }
 
-.media-modal__content {
-  background: $base-overlay-background;
+.media-modal__closer {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
+
+.media-modal__navigation {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  pointer-events: none;
+  transition: opacity 0.3s linear;
+  will-change: opacity;
+
+  * {
+    pointer-events: auto;
+  }
+
+  &.media-modal__navigation--hidden {
+    opacity: 0;
+
+    * {
+      pointer-events: none;
+    }
+  }
+}
+
+.media-modal__nav {
+  background: rgba($base-overlay-background, 0.5);
+  box-sizing: border-box;
+  border: 0;
+  color: $primary-text-color;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  font-size: 24px;
+  height: 20vmax;
+  margin: auto 0;
+  padding: 30px 15px;
+  position: absolute;
+  top: 0;
+  bottom: 0;
+}
+
+.media-modal__nav--left {
+  left: 0;
+}
+
+.media-modal__nav--right {
+  right: 0;
 }
 
 .media-modal__pagination {
@@ -3313,7 +3503,8 @@
   text-align: center;
   position: absolute;
   left: 0;
-  bottom: -40px;
+  bottom: 20px;
+  pointer-events: none;
 }
 
 .media-modal__page-dot {
@@ -3337,8 +3528,8 @@
 
 .media-modal__close {
   position: absolute;
-  right: 4px;
-  top: 4px;
+  right: 8px;
+  top: 8px;
   z-index: 100;
 }
 
@@ -3756,8 +3947,7 @@
 
 .boost-modal__action-bar,
 .confirmation-modal__action-bar,
-.mute-modal__action-bar,
-.report-modal__action-bar {
+.mute-modal__action-bar {
   display: flex;
   justify-content: space-between;
   background: $ui-secondary-color;
@@ -3801,21 +3991,94 @@
   vertical-align: middle;
 }
 
+.report-modal {
+  width: 90vw;
+  max-width: 700px;
+}
+
+.report-modal__container {
+  display: flex;
+  border-top: 1px solid $ui-secondary-color;
+
+  @media screen and (max-width: 480px) {
+    flex-wrap: wrap;
+    overflow-y: auto;
+  }
+}
+
 .report-modal__statuses,
 .report-modal__comment {
-  padding: 10px;
+  box-sizing: border-box;
+  width: 50%;
+
+  @media screen and (max-width: 480px) {
+    width: 100%;
+  }
 }
 
 .report-modal__statuses {
+  flex: 1 1 auto;
   min-height: 20vh;
   max-height: 40vh;
   overflow-y: auto;
   overflow-x: hidden;
+
+  @media screen and (max-width: 480px) {
+    max-height: 10vh;
+  }
 }
 
 .report-modal__comment {
+  padding: 20px;
+  border-right: 1px solid $ui-secondary-color;
+  max-width: 320px;
+
+  p {
+    font-size: 14px;
+    line-height: 20px;
+    margin-bottom: 20px;
+  }
+
   .setting-text {
-    margin-top: 10px;
+    display: block;
+    box-sizing: border-box;
+    width: 100%;
+    margin: 0;
+    color: $ui-base-color;
+    background: $white;
+    padding: 10px;
+    font-family: inherit;
+    font-size: 14px;
+    resize: vertical;
+    border: 0;
+    outline: 0;
+    border-radius: 4px;
+    border: 1px solid $ui-secondary-color;
+    margin-bottom: 20px;
+
+    &:focus {
+      border: 1px solid darken($ui-secondary-color, 8%);
+    }
+  }
+
+  .setting-toggle {
+    margin-top: 20px;
+    margin-bottom: 24px;
+
+    &__label {
+      color: $ui-base-color;
+      font-size: 14px;
+    }
+  }
+
+  @media screen and (max-width: 480px) {
+    padding: 10px;
+    max-width: 100%;
+    order: 2;
+
+    .setting-toggle {
+      margin-bottom: 4px;
+    }
   }
 }
 
@@ -3908,6 +4171,15 @@
   }
 }
 
+.report-modal__target {
+  padding: 20px;
+
+  .media-modal__close {
+    top: 19px;
+    right: 15px;
+  }
+}
+
 .loading-bar {
   background-color: $ui-highlight-color;
   height: 3px;
@@ -3954,45 +4226,59 @@
   border-radius: 4px;
   margin-top: 14px;
   overflow: hidden;
-}
 
-.attachment-list__icon {
-  flex: 0 0 auto;
-  color: $ui-base-lighter-color;
-  padding: 8px 18px;
-  cursor: default;
-  border-right: 1px solid lighten($ui-base-color, 8%);
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  font-size: 26px;
+  &__icon {
+    flex: 0 0 auto;
+    color: $ui-base-lighter-color;
+    padding: 8px 18px;
+    cursor: default;
+    border-right: 1px solid lighten($ui-base-color, 8%);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    font-size: 26px;
 
-  .fa {
-    display: block;
+    .fa {
+      display: block;
+    }
   }
-}
-
-.attachment-list__list {
-  list-style: none;
-  padding: 4px 0;
-  padding-left: 8px;
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
 
-  li {
-    display: block;
+  &__list {
+    list-style: none;
     padding: 4px 0;
+    padding-left: 8px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+
+    li {
+      display: block;
+      padding: 4px 0;
+    }
+
+    a {
+      text-decoration: none;
+      color: $ui-base-lighter-color;
+      font-weight: 500;
+
+      &:hover {
+        text-decoration: underline;
+      }
+    }
   }
 
-  a {
-    text-decoration: none;
-    color: $ui-base-lighter-color;
-    font-weight: 500;
+  &.compact {
+    border: 0;
+    margin-top: 4px;
 
-    &:hover {
-      text-decoration: underline;
+    .attachment-list__list {
+      padding: 0;
+      display: block;
+    }
+
+    .fa {
+      color: $ui-base-lighter-color;
     }
   }
 }
@@ -4002,6 +4288,7 @@
   box-sizing: border-box;
   margin-top: 8px;
   overflow: hidden;
+  border-radius: 4px;
   position: relative;
   width: 100%;
 }
@@ -4012,10 +4299,13 @@
   display: block;
   float: left;
   position: relative;
+  border-radius: 4px;
+  overflow: hidden;
 
   &.standalone {
     .media-gallery__item-gifv-thumbnail {
       transform: none;
+      top: 0;
     }
   }
 }
@@ -4024,14 +4314,19 @@
   cursor: zoom-in;
   display: block;
   text-decoration: none;
+  color: $ui-secondary-color;
   height: 100%;
   line-height: 0;
 
   &,
   img {
     width: 100%;
-    height: 100%;
+  }
+
+  img {
+    position: relative;
     object-fit: cover;
+    height: auto;
   }
 }
 
@@ -4140,7 +4435,7 @@
 
   &.inline {
     video {
-      object-fit: cover;
+      object-fit: contain;
       position: relative;
       top: 50%;
       transform: translateY(-50%);
@@ -4360,64 +4655,105 @@
 /* End Video Player */
 
 .account-gallery__container {
-  margin: -2px;
-  padding: 4px;
   display: flex;
+  justify-content: center;
   flex-wrap: wrap;
+  padding: 2px;
 }
 
 .account-gallery__item {
-  flex: 1 1 auto;
-  width: calc(100% / 3 - 4px);
-  height: 95px;
-  margin: 2px;
+  flex-grow: 1;
+  width: 50%;
+  overflow: hidden;
+  position: relative;
+
+  &::before {
+    content: "";
+    display: block;
+    padding-top: 100%;
+  }
 
   a {
     display: block;
-    width: 100%;
-    height: 100%;
+    width: calc(100% - 4px);
+    height: calc(100% - 4px);
+    margin: 2px;
+    top: 0;
+    left: 0;
     background-color: $base-overlay-background;
     background-size: cover;
     background-position: center;
-    position: relative;
-    color: inherit;
+    position: absolute;
+    color: $ui-primary-color;
     text-decoration: none;
+    border-radius: 4px;
 
     &:hover,
     &:active,
     &:focus {
       outline: 0;
+      color: $ui-secondary-color;
+
+      &::before {
+        content: "";
+        display: block;
+        width: 100%;
+        height: 100%;
+        background: rgba($base-overlay-background, 0.3);
+        border-radius: 4px;
+      }
     }
   }
+
+  &__icons {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    font-size: 24px;
+  }
 }
 
-.account-section-headline {
-  color: $ui-base-lighter-color;
-  background: lighten($ui-base-color, 2%);
-  border-bottom: 1px solid lighten($ui-base-color, 4%);
-  padding: 15px 10px;
-  font-size: 14px;
-  font-weight: 500;
-  position: relative;
+.account__section-headline {
+  background: darken($ui-base-color, 4%);
+  border-bottom: 1px solid lighten($ui-base-color, 8%);
   cursor: default;
+  display: flex;
 
-  &::before,
-  &::after {
+  a {
     display: block;
-    content: "";
-    position: absolute;
-    bottom: 0;
-    left: 18px;
-    width: 0;
-    height: 0;
-    border-style: solid;
-    border-width: 0 10px 10px;
-    border-color: transparent transparent lighten($ui-base-color, 4%);
-  }
+    flex: 1 1 auto;
+    color: $ui-primary-color;
+    padding: 15px 0;
+    font-size: 14px;
+    font-weight: 500;
+    text-align: center;
+    text-decoration: none;
+    position: relative;
 
-  &::after {
-    bottom: -1px;
-    border-color: transparent transparent $ui-base-color;
+    &.active {
+      color: $ui-secondary-color;
+
+      &::before,
+      &::after {
+        display: block;
+        content: "";
+        position: absolute;
+        bottom: 0;
+        left: 50%;
+        width: 0;
+        height: 0;
+        transform: translateX(-50%);
+        border-style: solid;
+        border-width: 0 10px 10px;
+        border-color: transparent transparent lighten($ui-base-color, 8%);
+      }
+
+      &::after {
+        bottom: -1px;
+        border-color: transparent transparent $ui-base-color;
+      }
+    }
   }
 }
 
@@ -4739,3 +5075,55 @@ noscript {
     margin-bottom: 0;
   }
 }
+
+.focal-point {
+  position: relative;
+  cursor: pointer;
+  overflow: hidden;
+
+  &.dragging {
+    cursor: move;
+  }
+
+  &__reticle {
+    position: absolute;
+    width: 100px;
+    height: 100px;
+    transform: translate(-50%, -50%);
+    background: url('~/images/reticle.png') no-repeat 0 0;
+    border-radius: 50%;
+    box-shadow: 0 0 0 9999em rgba($base-shadow-color, 0.35);
+  }
+
+  &__overlay {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    top: 0;
+    left: 0;
+  }
+}
+
+.floating-action-button {
+  position: fixed;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 3.9375rem;
+  height: 3.9375rem;
+  bottom: 1.3125rem;
+  right: 1.3125rem;
+  background: darken($ui-highlight-color, 3%);
+  color: $white;
+  border-radius: 50%;
+  font-size: 21px;
+  line-height: 21px;
+  text-decoration: none;
+  box-shadow: 2px 3px 9px rgba($base-shadow-color, 0.4);
+
+  &:hover,
+  &:focus,
+  &:active {
+    background: lighten($ui-highlight-color, 7%);
+  }
+}
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index af2589e23..6fa1fa38f 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -1,4 +1,4 @@
-.container {
+.container-alt {
   width: 700px;
   margin: 0 auto;
   margin-top: 40px;
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 2bef53cff..d74c5a4fd 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -22,16 +22,6 @@ code {
     margin-top: 4px;
   }
 
-  h4 {
-    text-transform: uppercase;
-    font-size: 13px;
-    font-weight: 500;
-    color: $ui-primary-color;
-    padding-bottom: 8px;
-    margin-bottom: 8px;
-    border-bottom: 1px solid lighten($ui-base-color, 8%);
-  }
-
   p.hint {
     margin-bottom: 15px;
     color: $ui-primary-color;
@@ -278,6 +268,11 @@ code {
   .actions {
     margin-top: 30px;
     display: flex;
+
+    &.actions--top {
+      margin-top: 0;
+      margin-bottom: 30px;
+    }
   }
 
   button,
@@ -568,3 +563,21 @@ code {
     margin-bottom: 4px;
   }
 }
+
+.alternative-login {
+  margin-top: 20px;
+  margin-bottom: 20px;
+
+  h4 {
+    font-size: 16px;
+    color: $ui-base-lighter-color;
+    text-align: center;
+    margin-bottom: 20px;
+    border: 0;
+    padding: 0;
+  }
+
+  .button {
+    display: block;
+  }
+}
diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss
index a35bbee79..442b143a0 100644
--- a/app/javascript/styles/mastodon/stream_entries.scss
+++ b/app/javascript/styles/mastodon/stream_entries.scss
@@ -65,6 +65,10 @@
     }
   }
 
+  .media-gallery__gifv__label {
+    bottom: 9px;
+  }
+
   .status.light {
     padding: 14px 14px 14px (48px + 14px * 2);
     position: relative;
@@ -142,10 +146,10 @@
 
       a.status__content__spoiler-link {
         color: $primary-text-color;
-        background: $ui-primary-color;
+        background: $ui-base-color;
 
         &:hover {
-          background: lighten($ui-primary-color, 8%);
+          background: lighten($ui-base-color, 8%);
         }
       }
     }
@@ -210,10 +214,10 @@
 
       a.status__content__spoiler-link {
         color: $primary-text-color;
-        background: $ui-primary-color;
+        background: $ui-base-color;
 
         &:hover {
-          background: lighten($ui-primary-color, 8%);
+          background: lighten($ui-base-color, 8%);
         }
       }
     }
@@ -256,16 +260,8 @@
   }
 
   .media-spoiler {
-    background: $ui-primary-color;
-    color: $white;
-    transition: all 100ms linear;
-
-    &:hover,
-    &:active,
-    &:focus {
-      background: darken($ui-primary-color, 5%);
-      color: unset;
-    }
+    background: $ui-base-color;
+    color: $ui-primary-color;
   }
 
   .pre-header {
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 0f9e4f263..9b00f0f52 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -44,6 +44,12 @@ class ActivityPub::Activity
         ActivityPub::Activity::Accept
       when 'Reject'
         ActivityPub::Activity::Reject
+      when 'Flag'
+        ActivityPub::Activity::Flag
+      when 'Add'
+        ActivityPub::Activity::Add
+      when 'Remove'
+        ActivityPub::Activity::Remove
       end
     end
   end
@@ -74,7 +80,7 @@ class ActivityPub::Activity
 
     # Only continue if the status is supposed to have
     # arrived in real-time
-    return unless @options[:override_timestamps]
+    return unless @options[:override_timestamps] || status.within_realtime_window?
 
     distribute_to_followers(status)
   end
diff --git a/app/lib/activitypub/activity/add.rb b/app/lib/activitypub/activity/add.rb
new file mode 100644
index 000000000..ea94d2f98
--- /dev/null
+++ b/app/lib/activitypub/activity/add.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ActivityPub::Activity::Add < ActivityPub::Activity
+  def perform
+    return unless @json['target'].present? && value_or_id(@json['target']) == @account.featured_collection_url
+
+    status = status_from_uri(object_uri)
+
+    return unless status.account_id == @account.id && !@account.pinned?(status)
+
+    StatusPin.create!(account: @account, status: status)
+  end
+end
diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb
index abf2b9b80..c8a358195 100644
--- a/app/lib/activitypub/activity/announce.rb
+++ b/app/lib/activitypub/activity/announce.rb
@@ -15,7 +15,8 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
       account: @account,
       reblog: original_status,
       uri: @json['id'],
-      created_at: @options[:override_timestamps] ? nil : @json['published']
+      created_at: @options[:override_timestamps] ? nil : @json['published'],
+      visibility: original_status.visibility
     )
 
     distribute(status)
@@ -35,6 +36,6 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
   end
 
   def announceable?(status)
-    status.public_visibility? || status.unlisted_visibility?
+    status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility?
   end
 end
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 64c429420..5a1c13d67 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -20,13 +20,12 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   private
 
   def process_status
-    media_attachments = process_attachments
+    status_params = process_status_params
 
     ApplicationRecord.transaction do
       @status = Status.create!(status_params)
 
       process_tags(@status)
-      attach_media(@status, media_attachments)
     end
 
     resolve_thread(@status)
@@ -40,7 +39,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     status
   end
 
-  def status_params
+  def process_status_params
     {
       uri: @object['id'],
       url: object_url || @object['id'],
@@ -54,6 +53,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
       visibility: visibility_from_audience,
       thread: replied_to_status,
       conversation: conversation_from_uri(@object['conversation']),
+      media_attachments: process_attachments.take(4),
     }
   end
 
@@ -108,7 +108,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   end
 
   def process_attachments
-    return if @object['attachment'].nil?
+    return [] if @object['attachment'].nil?
 
     media_attachments = []
 
@@ -116,7 +116,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
       next if unsupported_media_type?(attachment['mediaType']) || attachment['url'].blank?
 
       href             = Addressable::URI.parse(attachment['url']).normalize.to_s
-      media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence)
+      media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'])
       media_attachments << media_attachment
 
       next if skip_download?
@@ -132,13 +132,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     media_attachments
   end
 
-  def attach_media(status, media_attachments)
-    return if media_attachments.blank?
-
-    media = MediaAttachment.where(status_id: nil, id: media_attachments.take(4).map(&:id))
-    media.update(status_id: status.id)
-  end
-
   def resolve_thread(status)
     return unless status.reply? && status.thread.nil?
     ThreadResolveWorker.perform_async(status.id, in_reply_to_uri)
diff --git a/app/lib/activitypub/activity/flag.rb b/app/lib/activitypub/activity/flag.rb
new file mode 100644
index 000000000..36d3c5730
--- /dev/null
+++ b/app/lib/activitypub/activity/flag.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class ActivityPub::Activity::Flag < ActivityPub::Activity
+  def perform
+    target_accounts            = object_uris.map { |uri| account_from_uri(uri) }.compact.select(&:local?)
+    target_statuses_by_account = object_uris.map { |uri| status_from_uri(uri) }.compact.select(&:local?).group_by(&:account_id)
+
+    target_accounts.each do |target_account|
+      next if Report.where(account: @account, target_account: target_account).exists?
+
+      target_statuses = target_statuses_by_account[target_account.id]
+
+      ReportService.new.call(
+        @account,
+        target_account,
+        status_ids: target_statuses.nil? ? [] : target_statuses.map(&:id),
+        comment: @json['content'] || ''
+      )
+    end
+  end
+
+  def object_uris
+    @object_uris ||= Array(@object.is_a?(Array) ? @object.map { |item| value_or_id(item) } : value_or_id(@object))
+  end
+end
diff --git a/app/lib/activitypub/activity/reject.rb b/app/lib/activitypub/activity/reject.rb
index d815feeb6..28d472883 100644
--- a/app/lib/activitypub/activity/reject.rb
+++ b/app/lib/activitypub/activity/reject.rb
@@ -17,6 +17,8 @@ class ActivityPub::Activity::Reject < ActivityPub::Activity
 
     follow_request = FollowRequest.find_by(account: target_account, target_account: @account)
     follow_request&.reject!
+
+    UnfollowService.new.call(target_account, @account) if target_account.following?(@account)
   end
 
   def target_uri
diff --git a/app/lib/activitypub/activity/remove.rb b/app/lib/activitypub/activity/remove.rb
new file mode 100644
index 000000000..62a1e3196
--- /dev/null
+++ b/app/lib/activitypub/activity/remove.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class ActivityPub::Activity::Remove < ActivityPub::Activity
+  def perform
+    return unless @json['target'].present? && value_or_id(@json['target']) == @account.featured_collection_url
+
+    status = status_from_uri(object_uri)
+
+    return unless status.account_id == @account.id
+
+    pin = StatusPin.find_by(account: @account, status: status)
+    pin&.destroy!
+  end
+end
diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index 90d589d90..f19b04ae6 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -17,6 +17,8 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
         'conversation'              => 'ostatus:conversation',
         'toot'                      => 'http://joinmastodon.org/ns#',
         'Emoji'                     => 'toot:Emoji',
+        'focalPoint'                => { '@container' => '@list', '@id' => 'toot:focalPoint' },
+        'featured'                  => 'toot:featured',
       },
     ],
   }.freeze
diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb
index b2489711d..95e3365c2 100644
--- a/app/lib/exceptions.rb
+++ b/app/lib/exceptions.rb
@@ -4,6 +4,7 @@ module Mastodon
   class Error < StandardError; end
   class NotPermittedError < Error; end
   class ValidationError < Error; end
+  class HostValidationError < ValidationError; end
   class RaceConditionError < Error; end
 
   class UnexpectedResponseError < Error
diff --git a/app/lib/fast_geometry_parser.rb b/app/lib/fast_geometry_parser.rb
new file mode 100644
index 000000000..5209c2bc5
--- /dev/null
+++ b/app/lib/fast_geometry_parser.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class FastGeometryParser
+  def self.from_file(file)
+    width, height = FastImage.size(file.path)
+
+    raise Paperclip::Errors::NotIdentifiedByImageMagickError if width.nil?
+
+    Paperclip::Geometry.new(width, height)
+  end
+end
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index fe5ebfc36..700fd61c4 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -178,22 +178,7 @@ class FeedManager
   end
 
   def keyword_filter?(status, receiver_id)
-    text_matcher = Glitch::KeywordMute.text_matcher_for(receiver_id)
-    tag_matcher  = Glitch::KeywordMute.tag_matcher_for(receiver_id)
-
-    should_filter   = text_matcher.matches?(status.text)
-    should_filter ||= text_matcher.matches?(status.spoiler_text)
-    should_filter ||= tag_matcher.matches?(status.tags)
-
-    if status.reblog?
-      reblog = status.reblog
-
-      should_filter ||= text_matcher.matches?(reblog.text)
-      should_filter ||= text_matcher.matches?(reblog.spoiler_text)
-      should_filter ||= tag_matcher.matches?(status.tags)
-    end
-
-    should_filter
+    Glitch::KeywordMuteHelper.new(receiver_id).matches?(status)
   end
 
   def filter_from_mentions?(status, receiver_id)
@@ -243,6 +228,14 @@ class FeedManager
         return false
       end
     else
+      # A reblog may reach earlier than the original status because of the
+      # delay of the worker deliverying the original status, the late addition
+      # by merging timelines, and other reasons.
+      # If such a reblog already exists, just do not re-insert it into the feed.
+      rank = redis.zrevrank(reblog_key, status.id)
+
+      return false unless rank.nil?
+
       redis.zadd(timeline_key, status.id, status.id)
     end
 
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 8c0f8cebc..1df4ff8d4 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -19,6 +19,8 @@ class Formatter
 
     raw_content = status.text
 
+    return '' if raw_content.blank?
+
     unless status.local?
       html = reformat(raw_content)
       html = encode_custom_emojis(html, status.emojis) if options[:custom_emojify]
diff --git a/app/lib/frontmatter_handler.rb b/app/lib/frontmatter_handler.rb
index 83e5f465e..a95a1e294 100644
--- a/app/lib/frontmatter_handler.rb
+++ b/app/lib/frontmatter_handler.rb
@@ -189,27 +189,6 @@ class FrontmatterHandler
     case str[0]
     when '"'
       str[1..-2]
-        .gsub(/\\0/, "\u{00}")
-        .gsub(/\\a/, "\u{07}")
-        .gsub(/\\b/, "\u{08}")
-        .gsub(/\\t/, "\u{09}")
-        .gsub(/\\\u{09}/, "\u{09}")
-        .gsub(/\\n/, "\u{0a}")
-        .gsub(/\\v/, "\u{0b}")
-        .gsub(/\\f/, "\u{0c}")
-        .gsub(/\\r/, "\u{0d}")
-        .gsub(/\\e/, "\u{1b}")
-        .gsub(/\\ /, "\u{20}")
-        .gsub(/\\"/, "\u{22}")
-        .gsub(/\\\//, "\u{2f}")
-        .gsub(/\\\\/, "\u{5c}")
-        .gsub(/\\N/, "\u{85}")
-        .gsub(/\\_/, "\u{a0}")
-        .gsub(/\\L/, "\u{2028}")
-        .gsub(/\\P/, "\u{2029}")
-        .gsub(/\\x([0-9a-fA-F]{2})/mu) {|s| $1.to_i.chr Encoding::UTF_8}
-        .gsub(/\\u([0-9a-fA-F]{4})/mu) {|s| $1.to_i.chr Encoding::UTF_8}
-        .gsub(/\\U([0-9a-fA-F]{8})/mu) {|s| $1.to_i.chr Encoding::UTF_8}
     when "'"
       str[1..-2].gsub(/''/, "'")
     else
diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb
index b38407cd3..aa46267dc 100644
--- a/app/lib/ostatus/activity/creation.rb
+++ b/app/lib/ostatus/activity/creation.rb
@@ -29,7 +29,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
     # Skip if the reblogged status is not public
     return if cached_reblog && !(cached_reblog.public_visibility? || cached_reblog.unlisted_visibility?)
 
-    media_attachments = save_media
+    media_attachments = save_media.take(4)
 
     ApplicationRecord.transaction do
       status = Status.create!(
@@ -44,12 +44,12 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
         language: content_language,
         visibility: visibility_scope,
         conversation: find_or_create_conversation,
-        thread: thread? ? find_status(thread.first) || find_activitypub_status(thread.first, thread.second) : nil
+        thread: thread? ? find_status(thread.first) || find_activitypub_status(thread.first, thread.second) : nil,
+        media_attachments: media_attachments
       )
 
       save_mentions(status)
       save_hashtags(status)
-      attach_media(status, media_attachments)
       save_emojis(status)
     end
 
@@ -61,7 +61,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
     Rails.logger.debug "Queuing remote status #{status.id} (#{id}) for distribution"
 
     LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
-    DistributionWorker.perform_async(status.id) if @options[:override_timestamps]
+    DistributionWorker.perform_async(status.id) if @options[:override_timestamps] || status.within_realtime_window?
 
     status
   end
@@ -159,13 +159,6 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
     media_attachments
   end
 
-  def attach_media(parent, media_attachments)
-    return if media_attachments.blank?
-
-    media = MediaAttachment.where(status_id: nil, id: media_attachments.take(4).map(&:id))
-    media.update(status_id: parent.id)
-  end
-
   def save_emojis(parent)
     do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media?
 
diff --git a/app/lib/request.rb b/app/lib/request.rb
index 7671f4ffc..5776b3d78 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -1,5 +1,8 @@
 # frozen_string_literal: true
 
+require 'ipaddr'
+require 'socket'
+
 class Request
   REQUEST_TARGET = '(request-target)'
 
@@ -8,7 +11,7 @@ class Request
   def initialize(verb, url, **options)
     @verb    = verb
     @url     = Addressable::URI.parse(url).normalize
-    @options = options
+    @options = options.merge(socket_class: Socket)
     @headers = {}
 
     set_common_headers!
@@ -87,4 +90,18 @@ class Request
   def http_client
     HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
   end
+
+  class Socket < TCPSocket
+    class << self
+      def open(host, *args)
+        address = IPSocket.getaddress(host)
+        raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address)
+        super address, *args
+      end
+
+      alias new open
+    end
+  end
+
+  private_constant :Socket
 end
diff --git a/app/lib/sidekiq_error_handler.rb b/app/lib/sidekiq_error_handler.rb
new file mode 100644
index 000000000..23785cf05
--- /dev/null
+++ b/app/lib/sidekiq_error_handler.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class SidekiqErrorHandler
+  def call(*)
+    yield
+  rescue Mastodon::HostValidationError => e
+    Rails.logger.error "#{e.class}: #{e.message}"
+    Rails.logger.error e.backtrace.join("\n")
+    # Do not retry
+  end
+end
diff --git a/app/lib/status_filter.rb b/app/lib/status_filter.rb
index a6a050ce1..41d4381e5 100644
--- a/app/lib/status_filter.rb
+++ b/app/lib/status_filter.rb
@@ -9,6 +9,7 @@ class StatusFilter
   end
 
   def filtered?
+    return false if !account.nil? && account.id == status.account_id
     blocked_by_policy? || (account_present? && filtered_status?) || silenced_account?
   end
 
diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb
index 5f0176f27..c7afdacc2 100644
--- a/app/lib/user_settings_decorator.rb
+++ b/app/lib/user_settings_decorator.rb
@@ -15,20 +15,21 @@ class UserSettingsDecorator
   private
 
   def process_update
-    user.settings['notification_emails'] = merged_notification_emails if change?('notification_emails')
-    user.settings['interactions']        = merged_interactions if change?('interactions')
-    user.settings['default_privacy']     = default_privacy_preference if change?('setting_default_privacy')
-    user.settings['default_sensitive']   = default_sensitive_preference if change?('setting_default_sensitive')
-    user.settings['unfollow_modal']      = unfollow_modal_preference if change?('setting_unfollow_modal')
-    user.settings['boost_modal']         = boost_modal_preference if change?('setting_boost_modal')
+    user.settings['notification_emails']     = merged_notification_emails if change?('notification_emails')
+    user.settings['interactions']            = merged_interactions if change?('interactions')
+    user.settings['default_privacy']         = default_privacy_preference if change?('setting_default_privacy')
+    user.settings['default_sensitive']       = default_sensitive_preference if change?('setting_default_sensitive')
+    user.settings['unfollow_modal']          = unfollow_modal_preference if change?('setting_unfollow_modal')
+    user.settings['boost_modal']             = boost_modal_preference if change?('setting_boost_modal')
     user.settings['favourite_modal']         = favourite_modal_preference if change?('setting_favourite_modal')
-    user.settings['delete_modal']        = delete_modal_preference if change?('setting_delete_modal')
-    user.settings['auto_play_gif']       = auto_play_gif_preference if change?('setting_auto_play_gif')
-    user.settings['reduce_motion']       = reduce_motion_preference if change?('setting_reduce_motion')
-    user.settings['system_font_ui']      = system_font_ui_preference if change?('setting_system_font_ui')
-    user.settings['noindex']             = noindex_preference if change?('setting_noindex')
-    user.settings['flavour']             = flavour_preference if change?('setting_flavour')
-    user.settings['skin']                = skin_preference if change?('setting_skin')
+    user.settings['delete_modal']            = delete_modal_preference if change?('setting_delete_modal')
+    user.settings['auto_play_gif']           = auto_play_gif_preference if change?('setting_auto_play_gif')
+    user.settings['display_sensitive_media'] = display_sensitive_media_preference if change?('setting_display_sensitive_media')
+    user.settings['reduce_motion']           = reduce_motion_preference if change?('setting_reduce_motion')
+    user.settings['system_font_ui']          = system_font_ui_preference if change?('setting_system_font_ui')
+    user.settings['noindex']                 = noindex_preference if change?('setting_noindex')
+    user.settings['flavour']                 = flavour_preference if change?('setting_flavour')
+    user.settings['skin']                    = skin_preference if change?('setting_skin')
   end
 
   def merged_notification_emails
@@ -71,6 +72,10 @@ class UserSettingsDecorator
     boolean_cast_setting 'setting_auto_play_gif'
   end
 
+  def display_sensitive_media_preference
+    boolean_cast_setting 'setting_display_sensitive_media'
+  end
+
   def reduce_motion_preference
     boolean_cast_setting 'setting_reduce_motion'
   end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index 683b60c86..bf161b9be 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -3,6 +3,7 @@
 class ApplicationMailer < ActionMailer::Base
   layout 'mailer'
 
+  helper :application
   helper :instance
   helper :mailer
 
diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb
index 9fed4a636..b45844296 100644
--- a/app/mailers/notification_mailer.rb
+++ b/app/mailers/notification_mailer.rb
@@ -9,7 +9,7 @@ class NotificationMailer < ApplicationMailer
     @me     = recipient
     @status = notification.target_status
 
-    return if @me.user.disabled?
+    return if @me.user.disabled? || @status.nil?
 
     locale_for_account(@me) do
       thread_by_conversation(@status.conversation)
@@ -33,7 +33,7 @@ class NotificationMailer < ApplicationMailer
     @account = notification.from_account
     @status  = notification.target_status
 
-    return if @me.user.disabled?
+    return if @me.user.disabled? || @status.nil?
 
     locale_for_account(@me) do
       thread_by_conversation(@status.conversation)
@@ -46,7 +46,7 @@ class NotificationMailer < ApplicationMailer
     @account = notification.from_account
     @status  = notification.target_status
 
-    return if @me.user.disabled?
+    return if @me.user.disabled? || @status.nil?
 
     locale_for_account(@me) do
       thread_by_conversation(@status.conversation)
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 4104f6cd2..9848c34a2 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -3,6 +3,7 @@
 class UserMailer < Devise::Mailer
   layout 'mailer'
 
+  helper :application
   helper :instance
 
   add_template_helper RoutingHelper
@@ -65,4 +66,16 @@ class UserMailer < Devise::Mailer
       mail to: @resource.email, subject: I18n.t('user_mailer.welcome.subject')
     end
   end
+
+  def backup_ready(user, backup)
+    @resource = user
+    @instance = Rails.configuration.x.local_domain
+    @backup   = backup
+
+    return if @resource.disabled?
+
+    I18n.with_locale(@resource.locale || I18n.default_locale) do
+      mail to: @resource.email, subject: I18n.t('user_mailer.backup_ready.subject')
+    end
+  end
 end
diff --git a/app/models/account.rb b/app/models/account.rb
index 6df9668d5..61f81ab70 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -43,6 +43,7 @@
 #  protocol                :integer          default("ostatus"), not null
 #  memorial                :boolean          default(FALSE), not null
 #  moved_to_account_id     :integer
+#  featured_collection_url :string
 #
 
 class Account < ApplicationRecord
@@ -165,7 +166,7 @@ class Account < ApplicationRecord
 
   def refresh!
     return if local?
-    ResolveRemoteAccountService.new.call(acct)
+    ResolveAccountService.new.call(acct)
   end
 
   def unsuspend!
diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb
index abcc923b3..bc00b4f32 100644
--- a/app/models/account_domain_block.rb
+++ b/app/models/account_domain_block.rb
@@ -16,12 +16,16 @@ class AccountDomainBlock < ApplicationRecord
   belongs_to :account
   validates :domain, presence: true, uniqueness: { scope: :account_id }
 
-  after_create  :remove_blocking_cache
-  after_destroy :remove_blocking_cache
+  after_commit :remove_blocking_cache
+  after_commit :remove_relationship_cache
 
   private
 
   def remove_blocking_cache
     Rails.cache.delete("exclude_domains_for:#{account_id}")
   end
+
+  def remove_relationship_cache
+    Rails.cache.delete_matched("relationship:#{account_id}:*")
+  end
 end
diff --git a/app/models/backup.rb b/app/models/backup.rb
new file mode 100644
index 000000000..5a7e6a14d
--- /dev/null
+++ b/app/models/backup.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: backups
+#
+#  id                :integer          not null, primary key
+#  user_id           :integer
+#  dump_file_name    :string
+#  dump_content_type :string
+#  dump_file_size    :integer
+#  dump_updated_at   :datetime
+#  processed         :boolean          default(FALSE), not null
+#  created_at        :datetime         not null
+#  updated_at        :datetime         not null
+#
+
+class Backup < ApplicationRecord
+  belongs_to :user, inverse_of: :backups
+
+  has_attached_file :dump
+  do_not_validate_attachment_file_type :dump
+end
diff --git a/app/models/block.rb b/app/models/block.rb
index 441e6bca3..d6ecabd3b 100644
--- a/app/models/block.rb
+++ b/app/models/block.rb
@@ -12,14 +12,14 @@
 
 class Block < ApplicationRecord
   include Paginable
+  include RelationshipCacheable
 
   belongs_to :account
   belongs_to :target_account, class_name: 'Account'
 
   validates :account_id, uniqueness: { scope: :target_account_id }
 
-  after_create  :remove_blocking_cache
-  after_destroy :remove_blocking_cache
+  after_commit :remove_blocking_cache
 
   private
 
diff --git a/app/models/concerns/account_avatar.rb b/app/models/concerns/account_avatar.rb
index 8a5c9a22c..9e34a9461 100644
--- a/app/models/concerns/account_avatar.rb
+++ b/app/models/concerns/account_avatar.rb
@@ -7,8 +7,8 @@ module AccountAvatar
 
   class_methods do
     def avatar_styles(file)
-      styles = { original: '120x120#' }
-      styles[:static] = { format: 'png', convert_options: '-coalesce' } if file.content_type == 'image/gif'
+      styles = { original: { geometry: '400x400#', file_geometry_parser: FastGeometryParser } }
+      styles[:static] = { geometry: '400x400#', format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif'
       styles
     end
 
@@ -17,7 +17,7 @@ module AccountAvatar
 
   included do
     # Avatar upload
-    has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '-quality 80 -strip' }
+    has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '-strip' }, processors: [:lazy_thumbnail]
     validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
     validates_attachment_size :avatar, less_than: 2.megabytes
   end
diff --git a/app/models/concerns/account_header.rb b/app/models/concerns/account_header.rb
index aff2aa3f9..04c576b28 100644
--- a/app/models/concerns/account_header.rb
+++ b/app/models/concerns/account_header.rb
@@ -7,8 +7,8 @@ module AccountHeader
 
   class_methods do
     def header_styles(file)
-      styles = { original: '700x335#' }
-      styles[:static] = { format: 'png', convert_options: '-coalesce' } if file.content_type == 'image/gif'
+      styles = { original: { geometry: '700x335#', file_geometry_parser: FastGeometryParser } }
+      styles[:static] = { geometry: '700x335#', format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif'
       styles
     end
 
@@ -17,7 +17,7 @@ module AccountHeader
 
   included do
     # Header upload
-    has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '-quality 80 -strip' }
+    has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '-strip' }, processors: [:lazy_thumbnail]
     validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
     validates_attachment_size :header, less_than: 2.megabytes
   end
diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb
new file mode 100644
index 000000000..50288e700
--- /dev/null
+++ b/app/models/concerns/omniauthable.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Omniauthable
+  extend ActiveSupport::Concern
+
+  TEMP_EMAIL_PREFIX = 'change@me'
+  TEMP_EMAIL_REGEX = /\Achange@me/
+
+  included do
+    def omniauth_providers
+      Devise.omniauth_configs.keys
+    end
+
+    def email_verified?
+      email && email !~ TEMP_EMAIL_REGEX
+    end
+  end
+
+  class_methods do
+    def find_for_oauth(auth, signed_in_resource = nil)
+      # EOLE-SSO Patch
+      auth.uid = (auth.uid[0][:uid] || auth.uid[0][:user]) if auth.uid.is_a? Hashie::Array
+      identity = Identity.find_for_oauth(auth)
+
+      # If a signed_in_resource is provided it always overrides the existing user
+      # to prevent the identity being locked with accidentally created accounts.
+      # Note that this may leave zombie accounts (with no associated identity) which
+      # can be cleaned up at a later date.
+      user = signed_in_resource ? signed_in_resource : identity.user
+      user = create_for_oauth(auth) if user.nil?
+
+      if identity.user.nil?
+        identity.user = user
+        identity.save!
+      end
+
+      user
+    end
+
+    def create_for_oauth(auth)
+      # Check if the user exists with provided email if the provider gives us a
+      # verified email.  If no verified email was provided or the user already
+      # exists, we assign a temporary email and ask the user to verify it on
+      # the next step via Auth::ConfirmationsController.finish_signup
+
+      user = User.new(user_params_from_auth(auth))
+      user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/
+      user.skip_confirmation!
+      user.save!
+      user
+    end
+
+    private
+
+    def user_params_from_auth(auth)
+      strategy          = Devise.omniauth_configs[auth.provider.to_sym].strategy
+      assume_verified   = strategy.try(:security).try(:assume_email_is_verified)
+      email_is_verified = auth.info.verified || auth.info.verified_email || assume_verified
+      email             = auth.info.verified_email || auth.info.email
+      email             = email_is_verified && !User.exists?(email: auth.info.email) && email
+      display_name      = auth.info.full_name || [auth.info.first_name, auth.info.last_name].join(' ')
+
+      {
+        email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
+        password: Devise.friendly_token[0, 20],
+        account_attributes: {
+          username: ensure_unique_username(auth.uid),
+          display_name: display_name,
+        },
+      }
+    end
+
+    def ensure_unique_username(starting_username)
+      username = starting_username
+      i        = 0
+
+      while Account.exists?(username: username)
+        i       += 1
+        username = "#{starting_username}_#{i}"
+      end
+
+      username
+    end
+  end
+end
diff --git a/app/models/concerns/paginable.rb b/app/models/concerns/paginable.rb
index 6061bf9bd..66695677e 100644
--- a/app/models/concerns/paginable.rb
+++ b/app/models/concerns/paginable.rb
@@ -10,5 +10,14 @@ module Paginable
       query = query.where(arel_table[:id].gt(since_id)) if since_id.present?
       query
     }
+
+    # Differs from :paginate_by_max_id in that it gives the results immediately following min_id,
+    # whereas since_id gives the items with largest id, but with since_id as a cutoff.
+    # Results will be in ascending order by id.
+    scope :paginate_by_min_id, ->(limit, min_id = nil) {
+      query = reorder(arel_table[:id]).limit(limit)
+      query = query.where(arel_table[:id].gt(min_id)) if min_id.present?
+      query
+    }
   end
 end
diff --git a/app/models/concerns/relationship_cacheable.rb b/app/models/concerns/relationship_cacheable.rb
new file mode 100644
index 000000000..0d9359f7e
--- /dev/null
+++ b/app/models/concerns/relationship_cacheable.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module RelationshipCacheable
+  extend ActiveSupport::Concern
+
+  included do
+    after_commit :remove_relationship_cache
+  end
+
+  private
+
+  def remove_relationship_cache
+    Rails.cache.delete("relationship:#{account_id}:#{target_account_id}")
+    Rails.cache.delete("relationship:#{target_account_id}:#{account_id}")
+  end
+end
diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb
index 990035b34..020303a2f 100644
--- a/app/models/concerns/remotable.rb
+++ b/app/models/concerns/remotable.rb
@@ -28,7 +28,11 @@ module Remotable
           matches  = response.headers['content-disposition']&.match(/filename="([^"]*)"/)
           filename = matches.nil? ? parsed_url.path.split('/').last : matches[1]
           basename = SecureRandom.hex(8)
-          extname  = File.extname(filename)
+          extname = if filename.nil?
+                      ''
+                    else
+                      File.extname(filename)
+                    end
 
           send("#{attachment_name}=", StringIO.new(response.to_s))
           send("#{attachment_name}_file_name=", basename + extname)
diff --git a/app/models/favourite.rb b/app/models/favourite.rb
index 2b1271f31..fa1884b86 100644
--- a/app/models/favourite.rb
+++ b/app/models/favourite.rb
@@ -13,6 +13,8 @@
 class Favourite < ApplicationRecord
   include Paginable
 
+  update_index('statuses#status', :status) if Chewy.enabled?
+
   belongs_to :account, inverse_of: :favourites
   belongs_to :status,  inverse_of: :favourites, counter_cache: true
 
diff --git a/app/models/follow.rb b/app/models/follow.rb
index f953b8e3e..8e6fe537a 100644
--- a/app/models/follow.rb
+++ b/app/models/follow.rb
@@ -13,6 +13,7 @@
 
 class Follow < ApplicationRecord
   include Paginable
+  include RelationshipCacheable
 
   belongs_to :account, counter_cache: :following_count
 
diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb
index bd6c4a0b9..cde26ceed 100644
--- a/app/models/follow_request.rb
+++ b/app/models/follow_request.rb
@@ -13,6 +13,7 @@
 
 class FollowRequest < ApplicationRecord
   include Paginable
+  include RelationshipCacheable
 
   belongs_to :account
   belongs_to :target_account, class_name: 'Account'
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index dd629279c..32922e7f1 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -34,6 +34,8 @@ class Form::AdminSettings
     :activity_api_enabled=,
     :peers_api_enabled,
     :peers_api_enabled=,
+    :show_known_fediverse_at_about_page,
+    :show_known_fediverse_at_about_page=,
     to: Setting
   )
 end
diff --git a/app/models/form/migration.rb b/app/models/form/migration.rb
index b74987337..c2a8655e1 100644
--- a/app/models/form/migration.rb
+++ b/app/models/form/migration.rb
@@ -20,6 +20,6 @@ class Form::Migration
   private
 
   def set_account
-    self.account = (ResolveRemoteAccountService.new.call(acct) if account.nil? && acct.present?)
+    self.account = (ResolveAccountService.new.call(acct) if account.nil? && acct.present?)
   end
 end
diff --git a/app/models/glitch/keyword_mute_helper.rb b/app/models/glitch/keyword_mute_helper.rb
new file mode 100644
index 000000000..6d067947f
--- /dev/null
+++ b/app/models/glitch/keyword_mute_helper.rb
@@ -0,0 +1,27 @@
+require 'html2text'
+
+class Glitch::KeywordMuteHelper
+  attr_reader :text_matcher
+  attr_reader :tag_matcher
+
+  def initialize(receiver_id)
+    @text_matcher   = Glitch::KeywordMute.text_matcher_for(receiver_id)
+    @tag_matcher    = Glitch::KeywordMute.tag_matcher_for(receiver_id)
+  end
+
+  def matches?(status)
+    matchers_match?(status) || (status.reblog? && matchers_match?(status.reblog))
+  end
+
+  private
+
+  def matchers_match?(status)
+    text_matcher.matches?(prepare_text(status.text)) ||
+      text_matcher.matches?(prepare_text(status.spoiler_text)) ||
+      tag_matcher.matches?(status.tags)
+  end
+
+  def prepare_text(text)
+    Html2Text.convert(text)
+  end
+end
diff --git a/app/models/identity.rb b/app/models/identity.rb
new file mode 100644
index 000000000..a5e0c09ec
--- /dev/null
+++ b/app/models/identity.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: identities
+#
+#  id         :integer          not null, primary key
+#  user_id    :integer
+#  provider   :string           default(""), not null
+#  uid        :string           default(""), not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+
+class Identity < ApplicationRecord
+  belongs_to :user, dependent: :destroy
+  validates :uid, presence: true, uniqueness: { scope: :provider }
+  validates :provider, presence: true
+
+  def self.find_for_oauth(auth)
+    find_or_create_by(uid: auth.uid, provider: auth.provider)
+  end
+end
diff --git a/app/models/import.rb b/app/models/import.rb
index ba88435bf..fdb4c6b80 100644
--- a/app/models/import.rb
+++ b/app/models/import.rb
@@ -26,7 +26,7 @@ class Import < ApplicationRecord
 
   validates :type, presence: true
 
-  has_attached_file :data, url: '/system/:hash.:extension', hash_secret: ENV['PAPERCLIP_SECRET']
+  has_attached_file :data
   validates_attachment_content_type :data, content_type: FILE_TYPES
   validates_attachment_presence :data
 end
diff --git a/app/models/invite.rb b/app/models/invite.rb
index b87a3b722..4ba5432d2 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -4,7 +4,7 @@
 # Table name: invites
 #
 #  id         :integer          not null, primary key
-#  user_id    :integer
+#  user_id    :integer          not null
 #  code       :string           default(""), not null
 #  expires_at :datetime
 #  max_uses   :integer
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 25b7fd085..283d0e714 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -34,7 +34,18 @@ class MediaAttachment < ApplicationRecord
   VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze
   AUDIO_MIME_TYPES = ['audio/mpeg', 'audio/mp4', 'audio/vnd.wav', 'audio/wav', 'audio/x-wav', 'audio/x-wave', 'audio/ogg',].freeze
 
-  IMAGE_STYLES = { original: '1280x1280>', small: '400x400>' }.freeze
+  IMAGE_STYLES = {
+    original: {
+      geometry: '1280x1280>',
+      file_geometry_parser: FastGeometryParser,
+    },
+
+    small: {
+      geometry: '400x400>',
+      file_geometry_parser: FastGeometryParser,
+    },
+  }.freeze
+
   AUDIO_STYLES = {
     original: {
       format: 'mp4',
@@ -50,6 +61,7 @@ class MediaAttachment < ApplicationRecord
       },
     },
   }.freeze
+
   VIDEO_STYLES = {
     small: {
       convert_options: {
@@ -97,6 +109,24 @@ class MediaAttachment < ApplicationRecord
     shortcode
   end
 
+  def focus=(point)
+    return if point.blank?
+
+    x, y = (point.is_a?(Enumerable) ? point : point.split(',')).map(&:to_f)
+
+    meta = file.instance_read(:meta) || {}
+    meta['focus'] = { 'x' => x, 'y' => y }
+
+    file.instance_write(:meta, meta)
+  end
+
+  def focus
+    x = file.meta['focus']['x']
+    y = file.meta['focus']['y']
+
+    "#{x},#{y}"
+  end
+
   before_create :prepare_description, unless: :local?
   before_create :set_shortcode
   before_post_process :set_type_and_extension
@@ -178,26 +208,42 @@ class MediaAttachment < ApplicationRecord
   end
 
   def populate_meta
-    meta = {}
+    meta = file.instance_read(:meta) || {}
 
     file.queued_for_write.each do |style, file|
-      begin
-        geo = Paperclip::Geometry.from_file file
-
-        meta[style] = {
-          width: geo.width.to_i,
-          height: geo.height.to_i,
-          size: "#{geo.width.to_i}x#{geo.height.to_i}",
-          aspect: geo.width.to_f / geo.height.to_f,
-        }
-      rescue Paperclip::Errors::NotIdentifiedByImageMagickError
-        meta[style] = {}
-      end
+      meta[style] = style == :small || image? ? image_geometry(file) : video_metadata(file)
     end
 
     meta
   end
 
+  def image_geometry(file)
+    width, height = FastImage.size(file.path)
+
+    return {} if width.nil?
+
+    {
+      width:  width,
+      height: height,
+      size: "#{width}x#{height}",
+      aspect: width.to_f / height.to_f,
+    }
+  end
+
+  def video_metadata(file)
+    movie = FFMPEG::Movie.new(file.path)
+
+    return {} unless movie.valid?
+
+    {
+      width: movie.width,
+      height: movie.height,
+      frame_rate: movie.frame_rate,
+      duration: movie.duration,
+      bitrate: movie.bitrate,
+    }
+  end
+
   def appropriate_extension
     mime_type = MIME::Types[file.content_type]
 
diff --git a/app/models/mute.rb b/app/models/mute.rb
index da4787179..ebb3818c7 100644
--- a/app/models/mute.rb
+++ b/app/models/mute.rb
@@ -13,14 +13,14 @@
 
 class Mute < ApplicationRecord
   include Paginable
+  include RelationshipCacheable
 
   belongs_to :account
   belongs_to :target_account, class_name: 'Account'
 
   validates :account_id, uniqueness: { scope: :target_account_id }
 
-  after_create  :remove_blocking_cache
-  after_destroy :remove_blocking_cache
+  after_commit :remove_blocking_cache
 
   private
 
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 733f89cf7..7f8dae5ec 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -69,7 +69,7 @@ class Notification < ApplicationRecord
 
   class << self
     def reload_stale_associations!(cached_items)
-      account_ids = cached_items.map(&:from_account_id).uniq
+      account_ids = (cached_items.map(&:from_account_id) + cached_items.map { |item| item.target_status&.account_id }.compact).uniq
 
       return if account_ids.empty?
 
@@ -77,6 +77,7 @@ class Notification < ApplicationRecord
 
       cached_items.each do |item|
         item.from_account = accounts[item.from_account_id]
+        item.target_status.account = accounts[item.target_status.account_id] if item.target_status
       end
     end
 
diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb
index 716b82243..86eecdfe5 100644
--- a/app/models/preview_card.rb
+++ b/app/models/preview_card.rb
@@ -33,7 +33,7 @@ class PreviewCard < ApplicationRecord
 
   has_and_belongs_to_many :statuses
 
-  has_attached_file :image, styles: { original: '400x400>' }, convert_options: { all: '-quality 80 -strip' }
+  has_attached_file :image, styles: { original: { geometry: '400x400>', file_geometry_parser: FastGeometryParser } }, convert_options: { all: '-quality 80 -strip' }
 
   include Attachmentable
   include Remotable
@@ -58,10 +58,11 @@ class PreviewCard < ApplicationRecord
 
     return if file.nil?
 
-    geo         = Paperclip::Geometry.from_file(file)
-    self.width  = geo.width.to_i
-    self.height = geo.height.to_i
-  rescue Paperclip::Errors::NotIdentifiedByImageMagickError
-    nil
+    width, height = FastImage.size(file.path)
+
+    return nil if width.nil?
+
+    self.width  = width
+    self.height = height
   end
 end
diff --git a/app/models/report.rb b/app/models/report.rb
index f55fb6d3e..dd123fc15 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -24,6 +24,10 @@ class Report < ApplicationRecord
 
   validates :comment, length: { maximum: 1000 }
 
+  def object_type
+    :flag
+  end
+
   def statuses
     Status.where(id: status_ids).includes(:account, :media_attachments, :mentions)
   end
diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb
index 8ffdc8313..641128adf 100644
--- a/app/models/site_upload.rb
+++ b/app/models/site_upload.rb
@@ -34,8 +34,8 @@ class SiteUpload < ApplicationRecord
 
     return if tempfile.nil?
 
-    geometry  = Paperclip::Geometry.from_file(tempfile)
-    self.meta = { width: geometry.width.to_i, height: geometry.height.to_i }
+    width, height = FastImage.size(tempfile.path)
+    self.meta = { width: width, height: height }
   end
 
   def clear_cache
diff --git a/app/models/status.rb b/app/models/status.rb
index e927fb9dd..7e5ca09e4 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -32,6 +32,8 @@ class Status < ApplicationRecord
   include Cacheable
   include StatusThreadingConcern
 
+  update_index('statuses#status', :proper) if Chewy.enabled?
+
   enum visibility: [:public, :unlisted, :private, :direct], _suffix: :visibility
 
   belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
@@ -56,7 +58,7 @@ class Status < ApplicationRecord
   has_one :stream_entry, as: :activity, inverse_of: :status
 
   validates :uri, uniqueness: true, presence: true, unless: :local?
-  validates :text, presence: true, unless: :reblog?
+  validates :text, presence: true, unless: -> { with_media? || reblog? }
   validates_with StatusLengthValidator
   validates :reblog, uniqueness: { scope: :account }, if: :reblog?
 
@@ -77,10 +79,28 @@ class Status < ApplicationRecord
 
   scope :not_local_only, -> { where(local_only: [false, nil]) }
 
-  cache_associated :account, :application, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :application, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account
+  cache_associated :account, :application, :media_attachments, :conversation, :tags, :stream_entry, mentions: :account, reblog: [:account, :application, :stream_entry, :tags, :media_attachments, :conversation, mentions: :account], thread: :account
 
   delegate :domain, to: :account, prefix: true
 
+  REAL_TIME_WINDOW = 6.hours
+
+  def searchable_by(preloaded = nil)
+    ids = [account_id]
+
+    if preloaded.nil?
+      ids += mentions.pluck(:account_id)
+      ids += favourites.pluck(:account_id)
+      ids += reblogs.pluck(:account_id)
+    else
+      ids += preloaded.mentions[id] || []
+      ids += preloaded.favourites[id] || []
+      ids += preloaded.reblogs[id] || []
+    end
+
+    ids.uniq
+  end
+
   def reply?
     !in_reply_to_id.nil? || attributes['reply']
   end
@@ -93,6 +113,10 @@ class Status < ApplicationRecord
     !reblog_of_id.nil?
   end
 
+  def within_realtime_window?
+    created_at >= REAL_TIME_WINDOW.ago
+  end
+
   def verb
     if destroyed?
       :delete
@@ -129,8 +153,12 @@ class Status < ApplicationRecord
     private_visibility? || direct_visibility?
   end
 
+  def with_media?
+    media_attachments.any?
+  end
+
   def non_sensitive_with_media?
-    !sensitive? && media_attachments.any?
+    !sensitive? && with_media?
   end
 
   def emojis
diff --git a/app/models/user.rb b/app/models/user.rb
index 3cf9900bd..0346cf8ae 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -34,15 +34,17 @@
 #  disabled                  :boolean          default(FALSE), not null
 #  moderator                 :boolean          default(FALSE), not null
 #  invite_id                 :integer
+#  remember_token            :string
 #
 
 class User < ApplicationRecord
   include Settings::Extend
+  include Omniauthable
 
   ACTIVE_DURATION = 14.days
 
   devise :two_factor_authenticatable,
-         otp_secret_encryption_key: ENV['OTP_SECRET']
+         otp_secret_encryption_key: Rails.configuration.x.otp_secret
 
   devise :two_factor_backupable,
          otp_number_of_backup_codes: 10
@@ -50,11 +52,14 @@ class User < ApplicationRecord
   devise :registerable, :recoverable, :rememberable, :trackable, :validatable,
          :confirmable
 
+  devise :omniauthable
+
   belongs_to :account, inverse_of: :user
   belongs_to :invite, counter_cache: :uses, optional: true
   accepts_nested_attributes_for :account
 
   has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
+  has_many :backups, inverse_of: :user
 
   validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
   validates_with BlacklistedEmailValidator, if: :email_changed?
@@ -79,11 +84,44 @@ class User < ApplicationRecord
   has_many :session_activations, dependent: :destroy
 
   delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :favourite_modal, :delete_modal,
-           :reduce_motion, :system_font_ui, :noindex, :flavour, :skin,
+           :reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_sensitive_media,
            to: :settings, prefix: :setting, allow_nil: false
 
   attr_accessor :invite_code
 
+  def pam_conflict(_)
+    # block pam login tries on traditional account
+    nil
+  end
+
+  def pam_conflict?
+    return false unless Devise.pam_authentication
+    encrypted_password.present? && is_pam_account?
+  end
+
+  def pam_get_name
+    return account.username if account.present?
+    super
+  end
+
+  def pam_setup(_attributes)
+    acc = Account.new(username: pam_get_name)
+    acc.save!(validate: false)
+
+    self.email = "#{acc.username}@#{find_pam_suffix}" if email.nil? && find_pam_suffix
+    self.confirmed_at = Time.now.utc
+    self.admin = false
+    self.account = acc
+
+    acc.destroy! unless save
+  end
+
+  def ldap_setup(_attributes)
+    self.confirmed_at = Time.now.utc
+    self.admin = false
+    save!
+  end
+
   def confirmed?
     confirmed_at.present?
   end
@@ -129,7 +167,7 @@ class User < ApplicationRecord
     new_user = !confirmed?
 
     super
-    update_statistics! if new_user
+    prepare_new_user! if new_user
   end
 
   def confirm!
@@ -137,7 +175,12 @@ class User < ApplicationRecord
 
     skip_confirmation!
     save!
-    update_statistics! if new_user
+    prepare_new_user! if new_user
+  end
+
+  def update_tracked_fields!(request)
+    super
+    prepare_returning_user!
   end
 
   def promote!
@@ -208,6 +251,56 @@ class User < ApplicationRecord
     @invite_code = code
   end
 
+  def password_required?
+    return false if Devise.pam_authentication || Devise.ldap_authentication
+    super
+  end
+
+  def send_reset_password_instructions
+    return false if encrypted_password.blank? && (Devise.pam_authentication || Devise.ldap_authentication)
+    super
+  end
+
+  def reset_password!(new_password, new_password_confirmation)
+    return false if encrypted_password.blank? && (Devise.pam_authentication || Devise.ldap_authentication)
+    super
+  end
+
+  def self.pam_get_user(attributes = {})
+    if attributes[:email]
+      resource =
+        if Devise.check_at_sign && !attributes[:email].index('@')
+          joins(:account).find_by(accounts: { username: attributes[:email] })
+        else
+          find_by(email: attributes[:email])
+        end
+
+      if resource.blank?
+        resource = new(email: attributes[:email])
+        if Devise.check_at_sign && !resource[:email].index('@')
+          resource[:email] = "#{attributes[:email]}@#{resource.find_pam_suffix}"
+        end
+      end
+      resource
+    end
+  end
+
+  def self.ldap_get_user(attributes = {})
+    resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first })
+
+    if resource.blank?
+      resource = new(email: attributes[:mail].first, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first })
+      resource.ldap_setup(attributes)
+    end
+
+    resource
+  end
+
+  def self.authenticate_with_pam(attributes = {})
+    return nil unless Devise.pam_authentication
+    super
+  end
+
   protected
 
   def send_devise_notification(notification, *args)
@@ -220,9 +313,23 @@ class User < ApplicationRecord
     filtered_languages.reject!(&:blank?)
   end
 
-  def update_statistics!
+  def prepare_new_user!
     BootstrapTimelineWorker.perform_async(account_id)
     ActivityTracker.increment('activity:accounts:local')
     UserMailer.welcome(self).deliver_later
   end
+
+  def prepare_returning_user!
+    ActivityTracker.record('activity:logins', id)
+    regenerate_feed! if needs_feed_update?
+  end
+
+  def regenerate_feed!
+    Redis.current.setnx("account:#{account_id}:regeneration", true) && Redis.current.expire("account:#{account_id}:regeneration", 1.day.seconds)
+    RegenerationWorker.perform_async(account_id)
+  end
+
+  def needs_feed_update?
+    last_sign_in_at < ACTIVE_DURATION.ago
+  end
 end
diff --git a/app/models/web/setting.rb b/app/models/web/setting.rb
index 12b9d1226..0a5129d17 100644
--- a/app/models/web/setting.rb
+++ b/app/models/web/setting.rb
@@ -7,7 +7,7 @@
 #  data       :json
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  user_id    :integer
+#  user_id    :integer          not null
 #
 
 class Web::Setting < ApplicationRecord
diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb
index 3e617001f..d1de5e81a 100644
--- a/app/policies/application_policy.rb
+++ b/app/policies/application_policy.rb
@@ -15,4 +15,8 @@ class ApplicationPolicy
   def current_user
     current_account&.user
   end
+
+  def user_signed_in?
+    !current_user.nil?
+  end
 end
diff --git a/app/policies/backup_policy.rb b/app/policies/backup_policy.rb
new file mode 100644
index 000000000..0ef89a8d0
--- /dev/null
+++ b/app/policies/backup_policy.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class BackupPolicy < ApplicationPolicy
+  MIN_AGE = 1.week
+
+  def create?
+    user_signed_in? && current_user.backups.where('created_at >= ?', MIN_AGE.ago).count.zero?
+  end
+end
diff --git a/app/presenters/account_relationships_presenter.rb b/app/presenters/account_relationships_presenter.rb
index bf1ba3716..b1e99b31b 100644
--- a/app/presenters/account_relationships_presenter.rb
+++ b/app/presenters/account_relationships_presenter.rb
@@ -5,11 +5,67 @@ class AccountRelationshipsPresenter
               :muting, :requested, :domain_blocking
 
   def initialize(account_ids, current_account_id, **options)
-    @following       = Account.following_map(account_ids, current_account_id).merge(options[:following_map] || {})
-    @followed_by     = Account.followed_by_map(account_ids, current_account_id).merge(options[:followed_by_map] || {})
-    @blocking        = Account.blocking_map(account_ids, current_account_id).merge(options[:blocking_map] || {})
-    @muting          = Account.muting_map(account_ids, current_account_id).merge(options[:muting_map] || {})
-    @requested       = Account.requested_map(account_ids, current_account_id).merge(options[:requested_map] || {})
-    @domain_blocking = Account.domain_blocking_map(account_ids, current_account_id).merge(options[:domain_blocking_map] || {})
+    @account_ids        = account_ids.map { |a| a.is_a?(Account) ? a.id : a }
+    @current_account_id = current_account_id
+
+    @following       = cached[:following].merge(Account.following_map(@uncached_account_ids, @current_account_id))
+    @followed_by     = cached[:followed_by].merge(Account.followed_by_map(@uncached_account_ids, @current_account_id))
+    @blocking        = cached[:blocking].merge(Account.blocking_map(@uncached_account_ids, @current_account_id))
+    @muting          = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id))
+    @requested       = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id))
+    @domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id))
+
+    cache_uncached!
+
+    @following.merge!(options[:following_map] || {})
+    @followed_by.merge!(options[:followed_by_map] || {})
+    @blocking.merge!(options[:blocking_map] || {})
+    @muting.merge!(options[:muting_map] || {})
+    @requested.merge!(options[:requested_map] || {})
+    @domain_blocking.merge!(options[:domain_blocking_map] || {})
+  end
+
+  private
+
+  def cached
+    return @cached if defined?(@cached)
+
+    @cached = {
+      following: {},
+      followed_by: {},
+      blocking: {},
+      muting: {},
+      requested: {},
+      domain_blocking: {},
+    }
+
+    @uncached_account_ids = []
+
+    @account_ids.each do |account_id|
+      maps_for_account = Rails.cache.read("relationship:#{@current_account_id}:#{account_id}")
+
+      if maps_for_account.is_a?(Hash)
+        @cached.deep_merge!(maps_for_account)
+      else
+        @uncached_account_ids << account_id
+      end
+    end
+
+    @cached
+  end
+
+  def cache_uncached!
+    @uncached_account_ids.each do |account_id|
+      maps_for_account = {
+        following:       { account_id => following[account_id] },
+        followed_by:     { account_id => followed_by[account_id] },
+        blocking:        { account_id => blocking[account_id] },
+        muting:          { account_id => muting[account_id] },
+        requested:       { account_id => requested[account_id] },
+        domain_blocking: { account_id => domain_blocking[account_id] },
+      }
+
+      Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day)
+    end
   end
 end
diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb
index 1c08fb3bc..db288d5db 100644
--- a/app/presenters/instance_presenter.rb
+++ b/app/presenters/instance_presenter.rb
@@ -48,4 +48,8 @@ class InstancePresenter
   def thumbnail
     @thumbnail ||= Rails.cache.fetch('site_uploads/thumbnail') { SiteUpload.find_by(var: 'thumbnail') }
   end
+
+  def hero
+    @hero ||= Rails.cache.fetch('site_uploads/hero') { SiteUpload.find_by(var: 'hero') }
+  end
 end
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 622bdde0c..afcd37771 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -4,7 +4,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
   include RoutingHelper
 
   attributes :id, :type, :following, :followers,
-             :inbox, :outbox,
+             :inbox, :outbox, :featured,
              :preferred_username, :name, :summary,
              :url, :manually_approves_followers
 
@@ -53,6 +53,10 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
     account_outbox_url(object)
   end
 
+  def featured
+    account_collection_url(object, :featured)
+  end
+
   def endpoints
     object
   end
diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb
new file mode 100644
index 000000000..c0906e8d0
--- /dev/null
+++ b/app/serializers/activitypub/add_serializer.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class ActivityPub::AddSerializer < ActiveModel::Serializer
+  include RoutingHelper
+
+  attributes :type, :actor, :target
+  attribute :proper_object, key: :object
+
+  def type
+    'Add'
+  end
+
+  def actor
+    ActivityPub::TagManager.instance.uri_for(object.account)
+  end
+
+  def proper_object
+    ActivityPub::TagManager.instance.uri_for(object)
+  end
+
+  def target
+    account_collection_url(object.account, :featured)
+  end
+end
diff --git a/app/serializers/activitypub/collection_serializer.rb b/app/serializers/activitypub/collection_serializer.rb
index 9832133fc..1ae492945 100644
--- a/app/serializers/activitypub/collection_serializer.rb
+++ b/app/serializers/activitypub/collection_serializer.rb
@@ -2,7 +2,7 @@
 
 class ActivityPub::CollectionSerializer < ActiveModel::Serializer
   def self.serializer_for(model, options)
-    return ActivityPub::ActivitySerializer if model.class.name == 'Status'
+    return ActivityPub::NoteSerializer if model.class.name == 'Status'
     return ActivityPub::CollectionSerializer if model.class.name == 'ActivityPub::CollectionPresenter'
     super
   end
@@ -13,8 +13,8 @@ class ActivityPub::CollectionSerializer < ActiveModel::Serializer
   attribute :part_of, if: -> { object.part_of.present? }
 
   has_one :first, if: -> { object.first.present? }
-  has_many :items, key: :items, if: -> { (object.items.present? || page?) && !ordered? }
-  has_many :items, key: :ordered_items, if: -> { (object.items.present? || page?) && ordered? }
+  has_many :items, key: :items, if: -> { (!object.items.nil? || page?) && !ordered? }
+  has_many :items, key: :ordered_items, if: -> { (!object.items.nil? || page?) && ordered? }
 
   def type
     if page?
diff --git a/app/serializers/activitypub/flag_serializer.rb b/app/serializers/activitypub/flag_serializer.rb
new file mode 100644
index 000000000..53e8f726d
--- /dev/null
+++ b/app/serializers/activitypub/flag_serializer.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class ActivityPub::FlagSerializer < ActiveModel::Serializer
+  attributes :id, :type, :actor, :content
+  attribute :virtual_object, key: :object
+
+  def id
+    # This is nil for now
+    ActivityPub::TagManager.instance.uri_for(object)
+  end
+
+  def type
+    'Flag'
+  end
+
+  def actor
+    ActivityPub::TagManager.instance.uri_for(instance_options[:account] || object.account)
+  end
+
+  def virtual_object
+    [ActivityPub::TagManager.instance.uri_for(object.target_account)] + object.statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }
+  end
+
+  def content
+    object.comment
+  end
+end
diff --git a/app/serializers/activitypub/image_serializer.rb b/app/serializers/activitypub/image_serializer.rb
index a015c6b1b..3c08f77e8 100644
--- a/app/serializers/activitypub/image_serializer.rb
+++ b/app/serializers/activitypub/image_serializer.rb
@@ -4,6 +4,7 @@ class ActivityPub::ImageSerializer < ActiveModel::Serializer
   include RoutingHelper
 
   attributes :type, :media_type, :url
+  attribute :focal_point, if: :focal_point?
 
   def type
     'Image'
@@ -16,4 +17,12 @@ class ActivityPub::ImageSerializer < ActiveModel::Serializer
   def media_type
     object.content_type
   end
+
+  def focal_point?
+    object.respond_to?(:meta) && object.meta.is_a?(Hash) && object.meta['focus'].is_a?(Hash)
+  end
+
+  def focal_point
+    [object.meta['focus']['x'], object.meta['focus']['y']]
+  end
 end
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 24c39f3c9..d0e6290c1 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -90,6 +90,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
     include RoutingHelper
 
     attributes :type, :media_type, :url, :name
+    attribute :focal_point, if: :focal_point?
 
     def type
       'Document'
@@ -106,6 +107,14 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
     def url
       object.local? ? full_asset_url(object.file.url(:original, false)) : object.remote_url
     end
+
+    def focal_point?
+      object.file.meta.is_a?(Hash) && object.file.meta['focus'].is_a?(Hash)
+    end
+
+    def focal_point
+      [object.file.meta['focus']['x'], object.file.meta['focus']['y']]
+    end
   end
 
   class MentionSerializer < ActiveModel::Serializer
diff --git a/app/serializers/activitypub/outbox_serializer.rb b/app/serializers/activitypub/outbox_serializer.rb
new file mode 100644
index 000000000..48fbad0fd
--- /dev/null
+++ b/app/serializers/activitypub/outbox_serializer.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class ActivityPub::OutboxSerializer < ActivityPub::CollectionSerializer
+  def self.serializer_for(model, options)
+    return ActivityPub::ActivitySerializer if model.is_a?(Status)
+    super
+  end
+end
diff --git a/app/serializers/activitypub/remove_serializer.rb b/app/serializers/activitypub/remove_serializer.rb
new file mode 100644
index 000000000..c2a5ae1b3
--- /dev/null
+++ b/app/serializers/activitypub/remove_serializer.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class ActivityPub::RemoveSerializer < ActiveModel::Serializer
+  include RoutingHelper
+
+  attributes :type, :actor, :target
+  attribute :proper_object, key: :object
+
+  def type
+    'Remove'
+  end
+
+  def actor
+    ActivityPub::TagManager.instance.uri_for(object.account)
+  end
+
+  def proper_object
+    ActivityPub::TagManager.instance.uri_for(object)
+  end
+
+  def target
+    account_collection_url(object.account, :featured)
+  end
+end
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 904daa804..1d17e2b0a 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -22,16 +22,18 @@ class InitialStateSerializer < ActiveModel::Serializer
       locale: I18n.locale,
       domain: Rails.configuration.x.local_domain,
       admin: object.admin&.id&.to_s,
+      search_enabled: Chewy.enabled?,
     }
 
     if object.current_account
-      store[:me]             = object.current_account.id.to_s
-      store[:unfollow_modal] = object.current_account.user.setting_unfollow_modal
-      store[:boost_modal]    = object.current_account.user.setting_boost_modal
-      store[:favourite_modal]  = object.current_account.user.setting_favourite_modal
-      store[:delete_modal]   = object.current_account.user.setting_delete_modal
-      store[:auto_play_gif]  = object.current_account.user.setting_auto_play_gif
-      store[:reduce_motion]  = object.current_account.user.setting_reduce_motion
+      store[:me]                      = object.current_account.id.to_s
+      store[:unfollow_modal]          = object.current_account.user.setting_unfollow_modal
+      store[:boost_modal]             = object.current_account.user.setting_boost_modal
+      store[:favourite_modal]         = object.current_account.user.setting_favourite_modal
+      store[:delete_modal]            = object.current_account.user.setting_delete_modal
+      store[:auto_play_gif]           = object.current_account.user.setting_auto_play_gif
+      store[:display_sensitive_media] = object.current_account.user.setting_display_sensitive_media
+      store[:reduce_motion]           = object.current_account.user.setting_reduce_motion
     end
 
     store
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index 65907dad2..cab05e60a 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -4,7 +4,12 @@ class REST::InstanceSerializer < ActiveModel::Serializer
   include RoutingHelper
 
   attributes :uri, :title, :description, :email,
-             :version, :urls, :stats, :thumbnail, :max_toot_chars
+             :version, :urls, :stats, :thumbnail, :max_toot_chars,
+             :languages
+
+  has_one :contact_account, serializer: REST::AccountSerializer
+
+  delegate :contact_account, to: :instance_presenter
 
   def uri
     Rails.configuration.x.local_domain
@@ -46,6 +51,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
     { streaming_api: Rails.configuration.x.streaming_api_base_url }
   end
 
+  def languages
+    [I18n.default_locale]
+  end
+
   private
 
   def instance_presenter
diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb
index 3be110665..3860a9cbd 100644
--- a/app/services/account_search_service.rb
+++ b/app/services/account_search_service.rb
@@ -18,7 +18,7 @@ class AccountSearchService < BaseService
     return [] if query_blank_or_hashtag? || limit < 1
 
     if resolving_non_matching_remote_account?
-      [ResolveRemoteAccountService.new.call("#{query_username}@#{query_domain}")].compact
+      [ResolveAccountService.new.call("#{query_username}@#{query_domain}")].compact
     else
       search_results_and_exact_match.compact.uniq.slice(0, limit)
     end
diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb
new file mode 100644
index 000000000..40714e980
--- /dev/null
+++ b/app/services/activitypub/fetch_featured_collection_service.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+class ActivityPub::FetchFeaturedCollectionService < BaseService
+  include JsonLdHelper
+
+  def call(account)
+    @account = account
+    @json    = fetch_resource(@account.featured_collection_url, true)
+
+    return unless supported_context?
+    return if @account.suspended? || @account.local?
+
+    case @json['type']
+    when 'Collection', 'CollectionPage'
+      process_items @json['items']
+    when 'OrderedCollection', 'OrderedCollectionPage'
+      process_items @json['orderedItems']
+    end
+  end
+
+  private
+
+  def process_items(items)
+    status_ids = items.map { |item| value_or_id(item) }
+                      .reject { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }
+                      .map { |uri| ActivityPub::FetchRemoteStatusService.new.call(uri) }
+                      .compact
+                      .select { |status| status.account_id == @account.id }
+                      .map(&:id)
+
+    to_remove = []
+    to_add    = status_ids
+
+    StatusPin.where(account: @account).pluck(:status_id).each do |status_id|
+      if status_ids.include?(status_id)
+        to_add.delete(status_id)
+      else
+        to_remove << status_id
+      end
+    end
+
+    StatusPin.where(account: @account, status_id: to_remove).delete_all unless to_remove.empty?
+
+    to_add.each do |status_id|
+      StatusPin.create!(account: @account, status_id: status_id)
+    end
+  end
+
+  def supported_context?
+    super(@json)
+  end
+end
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index f43edafe7..68e9db766 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -27,6 +27,7 @@ class ActivityPub::ProcessAccountService < BaseService
 
     after_protocol_change! if protocol_changed?
     after_key_change! if key_changed?
+    check_featured_collection! if @account.featured_collection_url.present?
 
     @account
   rescue Oj::ParseError
@@ -57,14 +58,15 @@ class ActivityPub::ProcessAccountService < BaseService
   end
 
   def set_immediate_attributes!
-    @account.inbox_url        = @json['inbox'] || ''
-    @account.outbox_url       = @json['outbox'] || ''
-    @account.shared_inbox_url = (@json['endpoints'].is_a?(Hash) ? @json['endpoints']['sharedInbox'] : @json['sharedInbox']) || ''
-    @account.followers_url    = @json['followers'] || ''
-    @account.url              = url || @uri
-    @account.display_name     = @json['name'] || ''
-    @account.note             = @json['summary'] || ''
-    @account.locked           = @json['manuallyApprovesFollowers'] || false
+    @account.inbox_url               = @json['inbox'] || ''
+    @account.outbox_url              = @json['outbox'] || ''
+    @account.shared_inbox_url        = (@json['endpoints'].is_a?(Hash) ? @json['endpoints']['sharedInbox'] : @json['sharedInbox']) || ''
+    @account.followers_url           = @json['followers'] || ''
+    @account.featured_collection_url = @json['featured'] || ''
+    @account.url                     = url || @uri
+    @account.display_name            = @json['name'] || ''
+    @account.note                    = @json['summary'] || ''
+    @account.locked                  = @json['manuallyApprovesFollowers'] || false
   end
 
   def set_fetchable_attributes!
@@ -85,6 +87,10 @@ class ActivityPub::ProcessAccountService < BaseService
     RefollowWorker.perform_async(@account.id)
   end
 
+  def check_featured_collection!
+    ActivityPub::SynchronizeFeaturedCollectionWorker.perform_async(@account.id)
+  end
+
   def image_url(key)
     value = first_of_value(@json[key])
 
diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb
new file mode 100644
index 000000000..fadc24a82
--- /dev/null
+++ b/app/services/backup_service.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'rubygems/package'
+
+class BackupService < BaseService
+  attr_reader :account, :backup, :collection
+
+  def call(backup)
+    @backup  = backup
+    @account = backup.user.account
+
+    build_json!
+    build_archive!
+  end
+
+  private
+
+  def build_json!
+    @collection = serialize(collection_presenter, ActivityPub::CollectionSerializer)
+
+    account.statuses.with_includes.find_in_batches do |statuses|
+      statuses.each do |status|
+        item = serialize(status, ActivityPub::ActivitySerializer)
+        item.delete(:'@context')
+
+        unless item[:type] == 'Announce' || item[:object][:attachment].blank?
+          item[:object][:attachment].each do |attachment|
+            attachment[:url] = Addressable::URI.parse(attachment[:url]).path.gsub(/\A\/system\//, '')
+          end
+        end
+
+        @collection[:orderedItems] << item
+      end
+
+      GC.start
+    end
+  end
+
+  def build_archive!
+    tmp_file = Tempfile.new(%w(archive .tar.gz))
+
+    File.open(tmp_file, 'wb') do |file|
+      Zlib::GzipWriter.wrap(file) do |gz|
+        Gem::Package::TarWriter.new(gz) do |tar|
+          dump_media_attachments!(tar)
+          dump_outbox!(tar)
+          dump_actor!(tar)
+        end
+      end
+    end
+
+    archive_filename = ['archive', Time.now.utc.strftime('%Y%m%d%H%M%S'), SecureRandom.hex(2)].join('-') + '.tar.gz'
+
+    @backup.dump      = ActionDispatch::Http::UploadedFile.new(tempfile: tmp_file, filename: archive_filename)
+    @backup.processed = true
+    @backup.save!
+  ensure
+    tmp_file.close
+    tmp_file.unlink
+  end
+
+  def dump_media_attachments!(tar)
+    MediaAttachment.attached.where(account: account).find_in_batches do |media_attachments|
+      media_attachments.each do |m|
+        download_to_tar(tar, m.file, m.file.path)
+      end
+
+      GC.start
+    end
+  end
+
+  def dump_outbox!(tar)
+    json = Oj.dump(collection)
+
+    tar.add_file_simple('outbox.json', 0o444, json.bytesize) do |io|
+      io.write(json)
+    end
+  end
+
+  def dump_actor!(tar)
+    actor = serialize(account, ActivityPub::ActorSerializer)
+
+    actor[:icon][:url]  = 'avatar' + File.extname(actor[:icon][:url])  if actor[:icon]
+    actor[:image][:url] = 'header' + File.extname(actor[:image][:url]) if actor[:image]
+
+    download_to_tar(tar, account.avatar, 'avatar' + File.extname(account.avatar.path)) if account.avatar.exists?
+    download_to_tar(tar, account.header, 'header' + File.extname(account.header.path)) if account.header.exists?
+
+    json = Oj.dump(actor)
+
+    tar.add_file_simple('actor.json', 0o444, json.bytesize) do |io|
+      io.write(json)
+    end
+
+    tar.add_file_simple('key.pem', 0o444, account.private_key.bytesize) do |io|
+      io.write(account.private_key)
+    end
+  end
+
+  def collection_presenter
+    ActivityPub::CollectionPresenter.new(
+      id: account_outbox_url(account),
+      type: :ordered,
+      size: account.statuses_count,
+      items: []
+    )
+  end
+
+  def serialize(object, serializer)
+    ActiveModelSerializers::SerializableResource.new(
+      object,
+      serializer: serializer,
+      adapter: ActivityPub::Adapter
+    ).as_json
+  end
+
+  CHUNK_SIZE = 1.megabyte
+
+  def download_to_tar(tar, attachment, filename)
+    adapter = Paperclip.io_adapters.for(attachment)
+
+    tar.add_file_simple(filename, 0o444, adapter.size) do |io|
+      while (buffer = adapter.read(CHUNK_SIZE))
+        io.write(buffer)
+      end
+    end
+  end
+end
diff --git a/app/services/block_domain_service.rb b/app/services/block_domain_service.rb
index eefdc0dbf..d082de40b 100644
--- a/app/services/block_domain_service.rb
+++ b/app/services/block_domain_service.rb
@@ -5,13 +5,14 @@ class BlockDomainService < BaseService
 
   def call(domain_block)
     @domain_block = domain_block
-    process_domain_block
+    process_domain_block!
   end
 
   private
 
-  def process_domain_block
+  def process_domain_block!
     clear_media! if domain_block.reject_media?
+
     if domain_block.silence?
       silence_accounts!
     elsif domain_block.suspend?
@@ -19,14 +20,26 @@ class BlockDomainService < BaseService
     end
   end
 
+  def invalidate_association_caches!
+    # Normally, associated models of a status are immutable (except for accounts)
+    # so they are aggressively cached. After updating the media attachments to no
+    # longer point to a local file, we need to clear the cache to make those
+    # changes appear in the API and UI
+    @affected_status_ids.each { |id| Rails.cache.delete_matched("statuses/#{id}-*") }
+  end
+
   def silence_accounts!
     blocked_domain_accounts.in_batches.update_all(silenced: true)
   end
 
   def clear_media!
-    clear_account_images
-    clear_account_attachments
-    clear_emojos
+    @affected_status_ids = []
+
+    clear_account_images!
+    clear_account_attachments!
+    clear_emojos!
+
+    invalidate_association_caches!
   end
 
   def suspend_accounts!
@@ -36,23 +49,25 @@ class BlockDomainService < BaseService
     end
   end
 
-  def clear_account_images
+  def clear_account_images!
     blocked_domain_accounts.find_each do |account|
-      account.avatar.destroy
-      account.header.destroy
+      account.avatar.destroy if account.avatar.exists?
+      account.header.destroy if account.header.exists?
       account.save
     end
   end
 
-  def clear_account_attachments
+  def clear_account_attachments!
     media_from_blocked_domain.find_each do |attachment|
-      attachment.file.destroy
+      @affected_status_ids << attachment.status_id if attachment.status_id.present?
+
+      attachment.file.destroy if attachment.file.exists?
       attachment.type = :unknown
       attachment.save
     end
   end
 
-  def clear_emojos
+  def clear_emojos!
     emojis_from_blocked_domains.destroy_all
   end
 
diff --git a/app/services/concerns/author_extractor.rb b/app/services/concerns/author_extractor.rb
index c2366188a..1e00eb803 100644
--- a/app/services/concerns/author_extractor.rb
+++ b/app/services/concerns/author_extractor.rb
@@ -18,6 +18,6 @@ module AuthorExtractor
       acct   = "#{username}@#{domain}"
     end
 
-    ResolveRemoteAccountService.new.call(acct, update_profile)
+    ResolveAccountService.new.call(acct, update_profile)
   end
 end
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index d0472a1d7..8f252e64c 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -91,17 +91,19 @@ class FetchLinkCardService < BaseService
 
     case @card.type
     when 'link'
-      @card.image = URI.parse(embed.thumbnail_url) if embed.respond_to?(:thumbnail_url)
+      @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url)
     when 'photo'
       return false unless embed.respond_to?(:url)
-      @card.embed_url = embed.url
-      @card.image     = URI.parse(embed.url)
-      @card.width     = embed.width.presence  || 0
-      @card.height    = embed.height.presence || 0
+
+      @card.embed_url        = embed.url
+      @card.image_remote_url = embed.url
+      @card.width            = embed.width.presence  || 0
+      @card.height           = embed.height.presence || 0
     when 'video'
-      @card.width  = embed.width.presence  || 0
-      @card.height = embed.height.presence || 0
-      @card.html   = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED)
+      @card.width            = embed.width.presence  || 0
+      @card.height           = embed.height.presence || 0
+      @card.html             = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED)
+      @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url)
     when 'rich'
       # Most providers rely on <script> tags, which is a no-no
       return false
@@ -130,12 +132,12 @@ class FetchLinkCardService < BaseService
                                                scrolling: 'no',
                                                frameborder: '0')
     else
-      @card.type             = :link
-      @card.image_remote_url = meta_property(page, 'og:image') if meta_property(page, 'og:image')
+      @card.type = :link
     end
 
-    @card.title       = meta_property(page, 'og:title').presence || page.at_xpath('//title')&.content || ''
-    @card.description = meta_property(page, 'og:description').presence || meta_property(page, 'description') || ''
+    @card.title            = meta_property(page, 'og:title').presence || page.at_xpath('//title')&.content || ''
+    @card.description      = meta_property(page, 'og:description').presence || meta_property(page, 'description') || ''
+    @card.image_remote_url = meta_property(page, 'og:image') if meta_property(page, 'og:image')
 
     return if @card.title.blank? && @card.html.blank?
 
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
index ac0207a0a..60a389afd 100644
--- a/app/services/follow_service.rb
+++ b/app/services/follow_service.rb
@@ -9,7 +9,7 @@ class FollowService < BaseService
   # @param [true, false, nil] reblogs Whether or not to show reblogs, defaults to true
   def call(source_account, uri, reblogs: nil)
     reblogs = true if reblogs.nil?
-    target_account = uri.is_a?(Account) ? uri : ResolveRemoteAccountService.new.call(uri)
+    target_account = uri.is_a?(Account) ? uri : ResolveAccountService.new.call(uri)
 
     raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
     raise Mastodon::NotPermittedError  if target_account.blocking?(source_account) || source_account.blocking?(target_account)
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 6b6a37676..74b4cba0c 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -21,17 +21,18 @@ class PostStatusService < BaseService
 
     media  = validate_media!(options[:media_ids])
     status = nil
+    text   = options.delete(:spoiler_text) if text.blank? && options[:spoiler_text].present?
+    text   = '.' if text.blank? && !media.empty?
 
     ApplicationRecord.transaction do
       status = account.statuses.create!(text: text,
+                                        media_attachments: media || [],
                                         thread: in_reply_to,
                                         sensitive: options[:sensitive],
                                         spoiler_text: options[:spoiler_text] || '',
                                         visibility: options[:visibility] || account.user&.setting_default_privacy,
                                         language: LanguageDetector.instance.detect(text, account),
                                         application: options[:application])
-
-      attach_media(status, media)
     end
 
     process_mentions_service.call(status)
@@ -67,11 +68,6 @@ class PostStatusService < BaseService
     media
   end
 
-  def attach_media(status, media)
-    return if media.nil?
-    media.update(status_id: status.id)
-  end
-
   def process_mentions_service
     ProcessMentionsService.new
   end
diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb
index 46401f298..8e285e1f7 100644
--- a/app/services/process_mentions_service.rb
+++ b/app/services/process_mentions_service.rb
@@ -16,7 +16,7 @@ class ProcessMentionsService < BaseService
 
       if mention_undeliverable?(status, mentioned_account)
         begin
-          mentioned_account = resolve_remote_account_service.call($1)
+          mentioned_account = resolve_account_service.call($1)
         rescue Goldfinger::Error, HTTP::Error
           mentioned_account = nil
         end
@@ -63,7 +63,7 @@ class ProcessMentionsService < BaseService
     ).as_json).sign!(status.account))
   end
 
-  def resolve_remote_account_service
-    ResolveRemoteAccountService.new
+  def resolve_account_service
+    ResolveAccountService.new
   end
 end
diff --git a/app/services/report_service.rb b/app/services/report_service.rb
new file mode 100644
index 000000000..c06488a6d
--- /dev/null
+++ b/app/services/report_service.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+class ReportService < BaseService
+  def call(source_account, target_account, options = {})
+    @source_account = source_account
+    @target_account = target_account
+    @status_ids     = options.delete(:status_ids) || []
+    @comment        = options.delete(:comment) || ''
+    @options        = options
+
+    create_report!
+    notify_staff!
+    forward_to_origin! if !@target_account.local? && ActiveModel::Type::Boolean.new.cast(@options[:forward])
+
+    @report
+  end
+
+  private
+
+  def create_report!
+    @report = @source_account.reports.create!(
+      target_account: @target_account,
+      status_ids: @status_ids,
+      comment: @comment
+    )
+  end
+
+  def notify_staff!
+    User.staff.includes(:account).each do |u|
+      AdminMailer.new_report(u.account, @report).deliver_later
+    end
+  end
+
+  def forward_to_origin!
+    ActivityPub::DeliveryWorker.perform_async(
+      payload,
+      some_local_account.id,
+      @target_account.inbox_url
+    )
+  end
+
+  def payload
+    Oj.dump(ActiveModelSerializers::SerializableResource.new(
+      @report,
+      serializer: ActivityPub::FlagSerializer,
+      adapter: ActivityPub::Adapter,
+      account: some_local_account
+    ).as_json)
+  end
+
+  def some_local_account
+    @some_local_account ||= Account.local.where(suspended: false).first
+  end
+end
diff --git a/app/services/resolve_remote_account_service.rb b/app/services/resolve_account_service.rb
index d7d0be210..fd6d30605 100644
--- a/app/services/resolve_remote_account_service.rb
+++ b/app/services/resolve_account_service.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class ResolveRemoteAccountService < BaseService
+class ResolveAccountService < BaseService
   include OStatus2::MagicKey
   include JsonLdHelper
 
diff --git a/app/services/fetch_remote_resource_service.rb b/app/services/resolve_url_service.rb
index 6d40796f2..1f2b24524 100644
--- a/app/services/fetch_remote_resource_service.rb
+++ b/app/services/resolve_url_service.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-class FetchRemoteResourceService < BaseService
+class ResolveURLService < BaseService
   include JsonLdHelper
 
   attr_reader :url
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index 85ad94463..00a8b3dd7 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -1,21 +1,45 @@
 # frozen_string_literal: true
 
 class SearchService < BaseService
-  attr_accessor :query
+  attr_accessor :query, :account, :limit, :resolve
 
   def call(query, limit, resolve = false, account = nil)
-    @query = query
+    @query   = query
+    @account = account
+    @limit   = limit
+    @resolve = resolve
 
     default_results.tap do |results|
       if url_query?
-        results.merge!(remote_resource_results) unless remote_resource.nil?
+        results.merge!(url_resource_results) unless url_resource.nil?
       elsif query.present?
-        results[:accounts] = AccountSearchService.new.call(query, limit, account, resolve: resolve)
-        results[:hashtags] = Tag.search_for(query.gsub(/\A#/, ''), limit) unless query.start_with?('@')
+        results[:accounts] = perform_accounts_search! if account_searchable?
+        results[:statuses] = perform_statuses_search! if full_text_searchable?
+        results[:hashtags] = perform_hashtags_search! if hashtag_searchable?
       end
     end
   end
 
+  private
+
+  def perform_accounts_search!
+    AccountSearchService.new.call(query, limit, account, resolve: resolve)
+  end
+
+  def perform_statuses_search!
+    statuses = StatusesIndex.filter(term: { searchable_by: account.id })
+                            .query(multi_match: { type: 'most_fields', query: query, operator: 'and', fields: %w(text text.stemmed) })
+                            .limit(limit)
+                            .objects
+                            .compact
+
+    statuses.reject { |status| StatusFilter.new(status, account).filtered? }
+  end
+
+  def perform_hashtags_search!
+    Tag.search_for(query.gsub(/\A#/, ''), limit)
+  end
+
   def default_results
     { accounts: [], hashtags: [], statuses: [] }
   end
@@ -24,15 +48,28 @@ class SearchService < BaseService
     query =~ /\Ahttps?:\/\//
   end
 
-  def remote_resource_results
-    { remote_resource_symbol => [remote_resource] }
+  def url_resource_results
+    { url_resource_symbol => [url_resource] }
+  end
+
+  def url_resource
+    @_url_resource ||= ResolveURLService.new.call(query)
+  end
+
+  def url_resource_symbol
+    url_resource.class.name.downcase.pluralize.to_sym
+  end
+
+  def full_text_searchable?
+    return false unless Chewy.enabled?
+    !account.nil? && !((query.start_with?('#') || query.include?('@')) && !query.include?(' '))
   end
 
-  def remote_resource
-    @_remote_resource ||= FetchRemoteResourceService.new.call(query)
+  def account_searchable?
+    !(query.include?('@') && query.include?(' '))
   end
 
-  def remote_resource_symbol
-    remote_resource.class.name.downcase.pluralize.to_sym
+  def hashtag_searchable?
+    !query.include?('@')
   end
 end
diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb
index 79d17742a..2db9b29f6 100644
--- a/app/validators/status_length_validator.rb
+++ b/app/validators/status_length_validator.rb
@@ -23,6 +23,8 @@ class StatusLengthValidator < ActiveModel::Validator
   end
 
   def countable_text(status)
+    return '' if status.text.nil?
+
     status.text.dup.tap do |new_text|
       new_text.gsub!(FetchLinkCardService::URL_PATTERN, 'x' * 23)
       new_text.gsub!(Account::MENTION_RE, '@\2')
diff --git a/app/validators/unreserved_username_validator.rb b/app/validators/unreserved_username_validator.rb
index 44ea4359b..c2311a89a 100644
--- a/app/validators/unreserved_username_validator.rb
+++ b/app/validators/unreserved_username_validator.rb
@@ -8,7 +8,13 @@ class UnreservedUsernameValidator < ActiveModel::Validator
 
   private
 
+  def pam_controlled?(value)
+    return false unless Devise.pam_authentication && Devise.pam_controlled_service
+    Rpam2.account(Devise.pam_controlled_service, value).present?
+  end
+
   def reserved_username?(value)
+    return true if pam_controlled?(value)
     return false unless Setting.reserved_usernames
     Setting.reserved_usernames.include?(value.downcase)
   end
diff --git a/app/views/about/_forms.html.haml b/app/views/about/_forms.html.haml
new file mode 100644
index 000000000..81f7173f7
--- /dev/null
+++ b/app/views/about/_forms.html.haml
@@ -0,0 +1,15 @@
+- if @instance_presenter.open_registrations
+  = render 'registration'
+- else
+  = link_to t('auth.register_elsewhere'), 'https://joinmastodon.org', class: 'button button-primary'
+
+  .closed-registrations-message
+    - if @instance_presenter.closed_registrations_message.blank?
+      %p= t('about.closed_registrations')
+    - else
+      = @instance_presenter.closed_registrations_message.html_safe
+
+.separator-or
+  %span= t('auth.or')
+
+= link_to t('auth.login'), new_user_session_path, class: 'button button-alternative-2 webapp-btn'
diff --git a/app/views/about/_links.html.haml b/app/views/about/_links.html.haml
new file mode 100644
index 000000000..f79c37e65
--- /dev/null
+++ b/app/views/about/_links.html.haml
@@ -0,0 +1,16 @@
+.container-alt.links
+  .brand
+    = link_to root_url do
+      = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+
+  %ul.nav
+    %li
+      - if user_signed_in?
+        = link_to t('settings.back'), root_url, class: 'webapp-btn'
+      - else
+        = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
+    %li= link_to t('about.about_this'), about_more_path
+    %li
+      = link_to 'https://joinmastodon.org/' do
+        = "#{t('about.other_instances')}"
+        %i.fa.fa-external-link{ style: 'padding-left: 5px;' }
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
index 7a28f9738..6ca1d7129 100644
--- a/app/views/about/_registration.html.haml
+++ b/app/views/about/_registration.html.haml
@@ -10,6 +10,6 @@
   = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }
 
   .actions
-    = f.button :button, t('auth.register'), type: :submit, class: 'button button-alternative'
+    = f.button :button, t('auth.register'), type: :submit, class: 'button button-primary'
 
   %p.hint.subtle-hint=t('auth.agreement_html', rules_path: about_more_path, terms_path: terms_path)
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index d92362bd7..f86051fbf 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -7,51 +7,36 @@
 .landing-page
   .header-wrapper.compact
     .header
-      .container.links
-        .brand
-          = link_to root_url do
-            = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+      = render 'links'
 
-        %ul.nav
-          %li
-            - if user_signed_in?
-              = link_to t('settings.back'), root_url, class: 'webapp-btn'
-            - else
-              = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
-          %li= link_to t('about.about_this'), about_more_path
-          %li
-            = link_to 'https://joinmastodon.org/' do
-              = "#{t('about.other_instances')}"
-              %i.fa.fa-external-link{ style: 'padding-left: 5px;' }
-
-      .container.hero
+      .container-alt.hero
         .heading
           %h3= t('about.description_headline', domain: site_hostname)
           %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
 
   .information-board
-    .container
-      .information-board-sections
-        .section
+    .container-alt
+      .information-board__sections
+        .information-board__section
           %span= t 'about.user_count_before'
           %strong= number_with_delimiter @instance_presenter.user_count
           %span= t 'about.user_count_after'
-        .section
+        .information-board__section
           %span= t 'about.status_count_before'
           %strong= number_with_delimiter @instance_presenter.status_count
           %span= t 'about.status_count_after'
-        .section
+        .information-board__section
           %span= t 'about.domain_count_before'
           %strong= number_with_delimiter @instance_presenter.domain_count
           %span= t 'about.domain_count_after'
       = render 'contact', contact: @instance_presenter
 
   .extended-description
-    .container
+    .container-alt
       = @instance_presenter.site_extended_description.html_safe.presence || t('about.extended_description_html')
 
   .footer-links
-    .container
+    .container-alt
       %p
         = link_to t('about.source_code'), @instance_presenter.source_url
         - if @instance_presenter.commit_hash == ""
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index 4f5b53470..2f0b31a9f 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -2,80 +2,134 @@
   = site_hostname
 
 - content_for :header_tags do
+  %link{ rel: 'canonical', href: about_url }/
   %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
   = render partial: 'shared/og'
 
-.landing-page
-  .header-wrapper
-    .mascot-container
-      = image_tag asset_pack_path('elephant-fren.png'), alt: '', role: 'presentation', class: 'mascot'
-
-    .header
-      .container.links
+.landing-page.alternative
+  .container
+    .grid
+      .column-0
         .brand
           = link_to root_url do
             = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
 
-        %ul.nav
-          %li
-            - if user_signed_in?
-              = link_to t('settings.back'), root_url, class: 'webapp-btn'
-            - else
-              = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
-          %li= link_to t('about.about_this'), about_more_path
-          %li
-            = link_to 'https://joinmastodon.org/' do
-              = "#{t('about.other_instances')}"
-              %i.fa.fa-external-link{ style: 'padding-left: 5px;' }
-
-      .container.hero
-        .floats
-          %div{ role: 'presentation', class: 'float-1' }
-          %div{ role: 'presentation', class: 'float-2' }
-          %div{ role: 'presentation', class: 'float-3' }
-        .heading
-          %h1
-            = @instance_presenter.site_title
-            %small= t 'about.hosted_on', domain: site_hostname
-        - if @instance_presenter.open_registrations
-          = render 'registration'
-        - else
-          .closed-registrations-message
-            %div
-              - if @instance_presenter.closed_registrations_message.blank?
-                %p= t('about.closed_registrations')
-              - else
-                = @instance_presenter.closed_registrations_message.html_safe
-
-            = simple_form_for(:user, html: { style: 'margin-left: -20px' }, url: session_path(:user)) do |f|
-              = f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
-              = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
-
-              .actions
-                = f.button :button, t('auth.login'), type: :submit
-            = link_to t('about.find_another_instance'), 'https://joinmastodon.org/', class: 'button button-alternative button--block'
-
-  .about-short
-    .container
-      %h3= t('about.description_headline', domain: site_hostname)
-      %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
-
-  .features
-    .container
       - if Setting.timeline_preview
-        #mastodon-timeline{ data: { props: Oj.dump(default_props) } }
-
-      .about-mastodon
-        %h3= t 'about.what_is_mastodon'
-        %p= t 'about.about_mastodon_html'
-        = link_to t('about.learn_more'), 'https://joinmastodon.org/', class: 'button button-secondary'
-        = render 'features'
-  .footer-links
-    .container
-      %p
-        = link_to t('about.source_code'), @instance_presenter.source_url
-        - if @instance_presenter.commit_hash == ""
-          %strong= " (#{@instance_presenter.version_number})"
-        - else
-          %strong= " (#{@instance_presenter.version_number}, "
-          %strong= " #{@instance_presenter.commit_hash})"
+        .column-1
+          .landing-page__forms
+            .brand
+              = link_to root_url do
+                = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+
+            = render 'forms'
+
+      - else
+        .column-1.non-preview
+          .landing-page__forms
+            .brand
+              = link_to root_url do
+                = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+
+            = render 'forms'
+
+      - if Setting.timeline_preview
+        .column-2
+          .landing-page__hero
+            = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
+
+          .landing-page__information
+            .landing-page__short-description
+              .row
+                .landing-page__logo
+                  = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon'
+
+                %h1
+                  = @instance_presenter.site_title
+                  %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname)
+
+              %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
+
+          .landing-page__call-to-action
+            .row
+              .row__information-board
+                .information-board__section
+                  %span= t 'about.user_count_before'
+                  %strong= number_with_delimiter @instance_presenter.user_count
+                  %span= t 'about.user_count_after'
+                .information-board__section
+                  %span= t 'about.status_count_before'
+                  %strong= number_with_delimiter @instance_presenter.status_count
+                  %span= t 'about.status_count_after'
+              .row__mascot
+                .landing-page__mascot
+                  = image_tag asset_pack_path('elephant_ui_plane.svg')
+
+      - else
+        .column-2.non-preview
+          .landing-page__hero
+            = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
+
+          .landing-page__information
+            .landing-page__short-description
+              .row
+                .landing-page__logo
+                  = image_tag asset_pack_path('logo_transparent.svg'), alt: 'Mastodon'
+
+                %h1
+                  = @instance_presenter.site_title
+                  %small!= t 'about.hosted_on', domain: content_tag(:span, site_hostname)
+
+              %p= @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
+
+          .landing-page__call-to-action
+            .row
+              .row__information-board
+                .information-board__section
+                  %span= t 'about.user_count_before'
+                  %strong= number_with_delimiter @instance_presenter.user_count
+                  %span= t 'about.user_count_after'
+                .information-board__section
+                  %span= t 'about.status_count_before'
+                  %strong= number_with_delimiter @instance_presenter.status_count
+                  %span= t 'about.status_count_after'
+              .row__mascot
+                .landing-page__mascot
+                  = image_tag asset_pack_path('elephant_ui_plane.svg')
+
+      - if Setting.timeline_preview
+        .column-3
+          #mastodon-timeline{ data: { props: Oj.dump(default_props) } }
+
+      - if Setting.timeline_preview
+        .column-4.landing-page__information
+          .landing-page__features
+            .features-list
+              %div
+                %h3= t 'about.what_is_mastodon'
+                %p= t 'about.about_mastodon_html'
+
+            = render 'features'
+
+            .landing-page__features__action
+              = link_to t('about.learn_more'), 'https://joinmastodon.org/', class: 'button button-alternative'
+
+          .landing-page__footer
+            %p
+              = link_to t('about.source_code'), @instance_presenter.source_url
+              = " (#{@instance_presenter.version_number})"
+
+      - else
+        .column-4.non-preview.landing-page__information
+          .landing-page__features
+            %h3= t 'about.what_is_mastodon'
+            %p= t 'about.about_mastodon_html'
+
+            = render 'features'
+
+            .landing-page__features__action
+              = link_to t('about.learn_more'), 'https://joinmastodon.org/', class: 'button button-alternative'
+
+          .landing-page__footer
+            %p
+              = link_to t('about.source_code'), @instance_presenter.source_url
+              = " (#{@instance_presenter.version_number})"
diff --git a/app/views/about/terms.html.haml b/app/views/about/terms.html.haml
index 7004cb0b1..c7d36ed47 100644
--- a/app/views/about/terms.html.haml
+++ b/app/views/about/terms.html.haml
@@ -4,20 +4,8 @@
 .landing-page
   .header-wrapper.compact
     .header
-      .container.links
-        .brand
-          = link_to root_url do
-            = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
-
-        %ul.nav
-          %li
-            - if user_signed_in?
-              = link_to t('settings.back'), root_url, class: 'webapp-btn'
-            - else
-              = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
-          %li= link_to t('about.about_this'), about_more_path
-          %li= link_to t('about.other_instances'), 'https://joinmastodon.org/'
+      = render 'links'
 
   .extended-description
-    .container
+    .container-alt
       = @instance_presenter.site_terms.html_safe.presence || t('terms.body_html')
diff --git a/app/views/accounts/_follow_button.html.haml b/app/views/accounts/_follow_button.html.haml
new file mode 100644
index 000000000..e476e0aff
--- /dev/null
+++ b/app/views/accounts/_follow_button.html.haml
@@ -0,0 +1,23 @@
+- relationships ||= nil
+
+- unless account.memorial? || account.moved?
+  - if user_signed_in?
+    - requested = relationships ? relationships.requested[account.id].present? : current_account.requested?(account)
+    - following = relationships ? relationships.following[account.id].present? : current_account.following?(account)
+
+  - if user_signed_in? && current_account.id != account.id && !requested
+    .controls
+      - if following
+        = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do
+          = fa_icon 'user-times'
+          = t('accounts.unfollow')
+      - else
+        = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do
+          = fa_icon 'user-plus'
+          = t('accounts.follow')
+  - elsif !user_signed_in?
+    .controls
+      .remote-follow
+        = link_to account_remote_follow_path(account), class: 'icon-button' do
+          = fa_icon 'user-plus'
+          = t('accounts.remote_follow')
diff --git a/app/views/accounts/_grid_card.html.haml b/app/views/accounts/_grid_card.html.haml
index 305eb2c44..95acbd581 100644
--- a/app/views/accounts/_grid_card.html.haml
+++ b/app/views/accounts/_grid_card.html.haml
@@ -1,9 +1,12 @@
 .account-grid-card
   .account-grid-card__header{ style: "background-image: url(#{account.header.url(:original)})" }
+    = render 'accounts/follow_button', account: account, relationships: @relationships
   .account-grid-card__avatar
     .avatar= image_tag account.avatar.url(:original)
   .name
     = link_to TagManager.instance.url_for(account) do
       %span.display_name.emojify= display_name(account)
-      %span.username @#{account.acct}
-  %p.note.emojify= truncate(strip_tags(account.note), length: 150)
+      %span.username
+        @#{account.local? ? account.local_username_and_domain : account.acct}
+        = fa_icon('lock') if account.locked?
+  .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account)
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
index b0062752c..74251b923 100644
--- a/app/views/accounts/_header.html.haml
+++ b/app/views/accounts/_header.html.haml
@@ -1,24 +1,7 @@
 - processed_bio = FrontmatterHandler.instance.process_bio Formatter.instance.simplified_format account
 .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" }
   .card__illustration
-    - unless account.memorial? || account.moved?
-      - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account)
-        .controls
-          - if current_account.following?(account)
-            = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do
-              = fa_icon 'user-times'
-              = t('accounts.unfollow')
-          - else
-            = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do
-              = fa_icon 'user-plus'
-              = t('accounts.follow')
-      - elsif !user_signed_in?
-        .controls
-          .remote-follow
-            = link_to account_remote_follow_path(account), class: 'icon-button' do
-              = fa_icon 'user-plus'
-              = t('accounts.remote_follow')
-
+    = render 'accounts/follow_button', account: account
     .avatar= image_tag account.avatar.url(:original), class: 'u-photo'
 
   .card__bio
diff --git a/app/views/accounts/_og.html.haml b/app/views/accounts/_og.html.haml
index 1d16be590..26424a49c 100644
--- a/app/views/accounts/_og.html.haml
+++ b/app/views/accounts/_og.html.haml
@@ -1,7 +1,7 @@
 = opengraph 'og:url', url
 = opengraph 'og:site_name', site_title
 = opengraph 'og:title', [yield(:page_title).strip.presence, site_title].compact.join(' - ')
-= opengraph 'og:description', account.note
+= opengraph 'og:description', account_description(account)
 = opengraph 'og:image', full_asset_url(account.avatar.url(:original))
 = opengraph 'og:image:width', '120'
 = opengraph 'og:image:height', '120'
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index accad5f78..c62a573b0 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -1,7 +1,9 @@
 - content_for :page_title do
-  = display_name(@account)
+  = "#{display_name(@account)} (@#{@account.username})"
 
 - content_for :header_tags do
+  %meta{ name: 'description', content: account_description(@account) }/
+
   - if @account.user&.setting_noindex
     %meta{ name: 'robots', content: 'noindex' }/
 
@@ -9,6 +11,11 @@
   %link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/
   %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/
 
+  - if @older_url
+    %link{ rel: 'next', href: @older_url }/
+  - if @newer_url
+    %link{ rel: 'prev', href: @newer_url }/
+
   = opengraph 'og:type', 'profile'
   = render 'og', account: @account, url: short_account_url(@account, only_path: false)
 
@@ -39,6 +46,9 @@
 
       = render partial: 'stream_entries/status', collection: @statuses, as: :status
 
-  - if @statuses.size == 20
+  - if @newer_url || @older_url
     .pagination
-      = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), @next_url, class: 'next', rel: 'next'
+      - if @older_url
+        = link_to safe_join([fa_icon('chevron-left'), t('pagination.older')], ' '), @older_url, class: 'older', rel: 'next'
+      - if @newer_url
+        = link_to safe_join([t('pagination.newer'), fa_icon('chevron-right')], ' '), @newer_url, class: 'newer', rel: 'prev'
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 5f5d0995c..dbbf5fc09 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -18,7 +18,10 @@
         %tr
           %th= t('admin.accounts.role')
           %td
-            = t("admin.accounts.roles.#{@account.user&.role}")
+            - if @account.user.nil?
+              = t("admin.accounts.moderation.suspended")
+            - else
+              = t("admin.accounts.roles.#{@account.user&.role}")
             = table_link_to 'angle-double-up', t('admin.accounts.promote'), promote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:promote, @account.user)
             = table_link_to 'angle-double-down', t('admin.accounts.demote'), demote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:demote, @account.user)
         %tr
diff --git a/app/views/admin/settings/edit.html.haml b/app/views/admin/settings/edit.html.haml
index 4f9115ed2..08d05d738 100644
--- a/app/views/admin/settings/edit.html.haml
+++ b/app/views/admin/settings/edit.html.haml
@@ -12,6 +12,7 @@
 
   .fields-group
     = f.input :thumbnail, as: :file, wrapper: :with_block_label, label: t('admin.settings.thumbnail.title'), hint: t('admin.settings.thumbnail.desc_html')
+    = f.input :hero, as: :file, wrapper: :with_block_label, label: t('admin.settings.hero.title'), hint: t('admin.settings.hero.desc_html')
 
   %hr/
 
@@ -19,6 +20,9 @@
     = f.input :timeline_preview, as: :boolean, wrapper: :with_label, label: t('admin.settings.timeline_preview.title'), hint: t('admin.settings.timeline_preview.desc_html')
 
   .fields-group
+    = f.input :show_known_fediverse_at_about_page, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_known_fediverse_at_about_page.title'), hint: t('admin.settings.show_known_fediverse_at_about_page.desc_html')
+
+  .fields-group
     = f.input :show_staff_badge, as: :boolean, wrapper: :with_label, label: t('admin.settings.show_staff_badge.title'), hint: t('admin.settings.show_staff_badge.desc_html')
 
   .fields-group
diff --git a/app/views/auth/confirmations/finish_signup.html.haml b/app/views/auth/confirmations/finish_signup.html.haml
new file mode 100644
index 000000000..4b5161d6b
--- /dev/null
+++ b/app/views/auth/confirmations/finish_signup.html.haml
@@ -0,0 +1,14 @@
+- content_for :page_title do
+  = t('auth.confirm_email')
+
+= simple_form_for(current_user, as: 'user', url: finish_signup_path, html: { role: 'form'}) do |f|
+  - if @show_errors && current_user.errors.any?
+    #error_explanation
+      - current_user.errors.full_messages.each do |msg|
+        = msg
+        %br
+
+  = f.input :email
+
+  .actions
+    = f.submit t('auth.confirm_email'), class: 'button'
diff --git a/app/views/auth/passwords/edit.html.haml b/app/views/auth/passwords/edit.html.haml
index 5ef3de976..53d1769d6 100644
--- a/app/views/auth/passwords/edit.html.haml
+++ b/app/views/auth/passwords/edit.html.haml
@@ -3,12 +3,16 @@
 
 = simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
   = render 'shared/error_messages', object: resource
-  = f.input :reset_password_token, as: :hidden
 
-  = f.input :password, autofocus: true, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
-  = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
+  - if !use_seamless_external_login? || resource.encrypted_password.present?
+    = f.input :reset_password_token, as: :hidden
 
-  .actions
-    = f.button :button, t('auth.set_new_password'), type: :submit
+    = f.input :password, autofocus: true, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
+    = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
+
+    .actions
+      = f.button :button, t('auth.set_new_password'), type: :submit
+  - else
+    %p.hint= t('users.seamless_external_login')
 
 .form-footer= render 'auth/shared/links'
diff --git a/app/views/auth/registrations/_sessions.html.haml b/app/views/auth/registrations/_sessions.html.haml
index 8424a8901..8586c0549 100644
--- a/app/views/auth/registrations/_sessions.html.haml
+++ b/app/views/auth/registrations/_sessions.html.haml
@@ -1,4 +1,4 @@
-%h6= t 'sessions.title'
+%h4= t 'sessions.title'
 %p.muted-hint= t 'sessions.explanation'
 
 .table-wrapper
diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml
index 145f5cd9e..05fc7df31 100644
--- a/app/views/auth/registrations/edit.html.haml
+++ b/app/views/auth/registrations/edit.html.haml
@@ -1,23 +1,24 @@
 - content_for :page_title do
-  = t('auth.change_password')
+  = t('auth.security')
 
+%h4= t('auth.change_password')
 = simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'auth_edit' }) do |f|
   = render 'shared/error_messages', object: resource
 
-  = f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
-  = f.input :password, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
-  = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
-  = f.input :current_password, placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }
+  - if !use_seamless_external_login? || resource.encrypted_password.present?
+    = f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
+    = f.input :password, placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'off' }
+    = f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'off' }
+    = f.input :current_password, placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'off' }
 
-  .actions
-    = f.button :button, t('generic.save_changes'), type: :submit
-
-%hr/
+    .actions
+      = f.button :button, t('generic.save_changes'), type: :submit
+  - else
+    %p.hint= t('users.seamless_external_login')
 
 = render 'sessions'
 
 - if open_deletion?
-  %hr/
 
-  %h6= t('auth.delete_account')
+  %h4= t('auth.delete_account')
   %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml
index a52b0053b..0c9f9d5fe 100644
--- a/app/views/auth/sessions/new.html.haml
+++ b/app/views/auth/sessions/new.html.haml
@@ -5,10 +5,22 @@
   = render partial: 'shared/og'
 
 = simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
-  = f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
+  - if use_seamless_external_login?
+    = f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.username_or_email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username_or_email') }
+  - else
+    = f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
   = f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off' }
 
   .actions
     = f.button :button, t('auth.login'), type: :submit
 
+- if devise_mapping.omniauthable? and resource_class.omniauth_providers.any?
+  .simple_form.alternative-login
+    %h4= t('auth.or_log_in_with')
+
+    .actions
+      - resource_class.omniauth_providers.each do |provider|
+        = link_to omniauth_authorize_path(resource_name, provider), class: "button button-#{provider}" do
+          = t("auth.providers.#{provider}", default: provider.to_s.chomp("_oauth2").capitalize)
+
 .form-footer= render 'auth/shared/links'
diff --git a/app/views/authorize_follows/success.html.haml b/app/views/authorize_follows/success.html.haml
index f0b495689..63ff3bcf1 100644
--- a/app/views/authorize_follows/success.html.haml
+++ b/app/views/authorize_follows/success.html.haml
@@ -12,5 +12,5 @@
 
   .post-follow-actions
     %div= link_to t('authorize_follow.post_follow.web'), web_url("accounts/#{@account.id}"), class: 'button button--block'
-    %div= link_to t('authorize_follow.post_follow.return'), @account.url, class: 'button button--block'
+    %div= link_to t('authorize_follow.post_follow.return'), TagManager.instance.url_for(@account), class: 'button button--block'
     %div= t('authorize_follow.post_follow.close')
diff --git a/app/views/follower_accounts/index.html.haml b/app/views/follower_accounts/index.html.haml
index 738b31638..a24e4ea20 100644
--- a/app/views/follower_accounts/index.html.haml
+++ b/app/views/follower_accounts/index.html.haml
@@ -2,9 +2,7 @@
   = t('accounts.people_who_follow', name: display_name(@account))
 
 - content_for :header_tags do
-  - if @account.user&.setting_noindex
-    %meta{ name: 'robots', content: 'noindex' }/
-
+  %meta{ name: 'robots', content: 'noindex' }/
   = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
 
 = render 'accounts/header', account: @account
diff --git a/app/views/following_accounts/index.html.haml b/app/views/following_accounts/index.html.haml
index 9637c689f..67f6cfede 100644
--- a/app/views/following_accounts/index.html.haml
+++ b/app/views/following_accounts/index.html.haml
@@ -2,9 +2,7 @@
   = t('accounts.people_followed_by', name: display_name(@account))
 
 - content_for :header_tags do
-  - if @account.user&.setting_noindex
-    %meta{ name: 'robots', content: 'noindex' }/
-
+  %meta{ name: 'robots', content: 'noindex' }/
   = render 'accounts/og', account: @account, url: account_followers_url(@account, only_path: false)
 
 = render 'accounts/header', account: @account
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 322d7403e..9ede598b3 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -2,7 +2,7 @@
 %html{ lang: I18n.locale }
   %head
     %meta{ charset: 'utf-8' }/
-    %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }/   
+    %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }/
     %link{ rel: 'icon', href: favicon_path, type: 'image/x-icon' }/
     %link{ rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }/
     %link{ rel: 'mask-icon', href: '/mask-icon.svg', color: '#2B90D9' }/
@@ -11,11 +11,7 @@
     %meta{ name: 'theme-color', content: '#282c37' }/
     %meta{ name: 'apple-mobile-web-app-capable', content: 'yes' }/
 
-    %title<
-      - if content_for?(:page_title)
-        = yield(:page_title)
-        = ' - '
-      = title
+    %title= content_for?(:page_title) ? safe_join([yield(:page_title).chomp.html_safe, title], ' - ') : title
 
     = javascript_pack_tag "locales", integrity: true, crossorigin: 'anonymous'
     - if @theme
diff --git a/app/views/layouts/auth.html.haml b/app/views/layouts/auth.html.haml
index f4812ac6a..ca9c13945 100644
--- a/app/views/layouts/auth.html.haml
+++ b/app/views/layouts/auth.html.haml
@@ -1,5 +1,5 @@
 - content_for :content do
-  .container
+  .container-alt
     .logo-container
       %h1
         = link_to root_path do
diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml
index ad15754d5..6321fec61 100644
--- a/app/views/layouts/mailer.html.haml
+++ b/app/views/layouts/mailer.html.haml
@@ -7,7 +7,7 @@
     %title/
 
     = stylesheet_pack_tag 'core/mailer'
-  %body
+  %body{ dir: locale_direction }
     %table.email-table{ cellspacing: 0, cellpadding: 0 }
       %tbody
         %tr
diff --git a/app/views/layouts/modal.html.haml b/app/views/layouts/modal.html.haml
index a5d79f5c0..e808593cd 100644
--- a/app/views/layouts/modal.html.haml
+++ b/app/views/layouts/modal.html.haml
@@ -8,7 +8,7 @@
       = link_to destroy_user_session_path, method: :delete, class: 'logout-link icon-button' do
         = fa_icon 'sign-out'
 
-  .container= yield
+  .container-alt= yield
   .modal-layout__mastodon
     %div
 
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index b3795eaad..07441a77d 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -1,5 +1,5 @@
 - content_for :content do
-  .container= yield
+  .container-alt= yield
   .footer
     - if !user_signed_in? && single_user_mode?
       %span.single-user-login
diff --git a/app/views/media/player.html.haml b/app/views/media/player.html.haml
new file mode 100644
index 000000000..ea868b3f6
--- /dev/null
+++ b/app/views/media/player.html.haml
@@ -0,0 +1,2 @@
+%video{ poster: @media_attachment.file.url(:small), preload: 'auto', autoplay: 'autoplay', muted: 'muted', loop: 'loop', controls: 'controls', style: "width: #{@media_attachment.file.meta.dig('original', 'width')}px; height: #{@media_attachment.file.meta.dig('original', 'height')}px" }
+  %source{ src: @media_attachment.file.url(:original), type: @media_attachment.file_content_type }
diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml
index 85f9294e9..f82ada146 100644
--- a/app/views/notification_mailer/_status.html.haml
+++ b/app/views/notification_mailer/_status.html.haml
@@ -1,6 +1,6 @@
 - i ||= 0
 
-%table.email-table{ cellspacing: 0, cellpadding: 0 }
+%table.email-table{ cellspacing: 0, cellpadding: 0, dir: 'ltr' }
   %tbody
     %tr
       %td.email-body
@@ -19,12 +19,13 @@
                                 %tbody
                                   %tr
                                     %td{ align: 'left', width: 48 }
-                                      = image_tag full_asset_url(status.account.avatar), alt:''
+                                      = image_tag full_asset_url(status.account.avatar.url), alt:''
                                     %td{ align: 'left' }
                                       %bdi= display_name(status.account)
                                       = "@#{status.account.acct}"
 
-                              = Formatter.instance.format(status)
+                              %div{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }
+                                = Formatter.instance.format(status)
 
                               %p.status-footer
                                 = link_to l(status.created_at), web_url("statuses/#{status.id}")
diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml
index e0df1c480..89d768d3f 100644
--- a/app/views/settings/exports/show.html.haml
+++ b/app/views/settings/exports/show.html.haml
@@ -20,3 +20,26 @@
         %th= t('exports.mutes')
         %td= @export.total_mutes
         %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv)
+
+%p.muted-hint= t('exports.archive_takeout.hint_html')
+
+- if policy(:backup).create?
+  %p= link_to t('exports.archive_takeout.request'), settings_export_path, class: 'button', method: :post
+
+- unless @backups.empty?
+  .table-wrapper
+    %table.table
+      %thead
+        %tr
+          %th= t('exports.archive_takeout.date')
+          %th= t('exports.archive_takeout.size')
+          %th
+      %tbody
+        - @backups.each do |backup|
+          %tr
+            %td= l backup.created_at
+            - if backup.processed?
+              %td= number_to_human_size backup.dump_file_size
+              %td= table_link_to 'download', t('exports.archive_takeout.download'), backup.dump.url
+            - else
+              %td{ colspan: 2 }= t('exports.archive_takeout.in_progress')
diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml
index d1459d93c..102e4d200 100644
--- a/app/views/settings/preferences/show.html.haml
+++ b/app/views/settings/preferences/show.html.haml
@@ -4,6 +4,9 @@
 = simple_form_for current_user, url: settings_preferences_path, html: { method: :put } do |f|
   = render 'shared/error_messages', object: current_user
 
+  .actions.actions--top
+    = f.button :button, t('generic.save_changes'), type: :submit
+
   %h4= t 'preferences.languages'
 
   .fields-group
@@ -33,6 +36,7 @@
 
   .fields-group
     = f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label
+    = f.input :setting_display_sensitive_media, as: :boolean, wrapper: :with_label
     = f.input :setting_reduce_motion, as: :boolean, wrapper: :with_label
     = f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label
 
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index d88ec8280..e1122d5a2 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -22,9 +22,9 @@
   - if !status.media_attachments.empty?
     - if status.media_attachments.first.video?
       - video = status.media_attachments.first
-      %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380, detailed: true) }}<
+      %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 670, height: 380, detailed: true, inline: true) }}
     - else
-      %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}<
+      %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}
   - elsif status.preview_cards.first
     %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }}
 
diff --git a/app/views/stream_entries/_og_description.html.haml b/app/views/stream_entries/_og_description.html.haml
index d2fa99e63..9c24e0a61 100644
--- a/app/views/stream_entries/_og_description.html.haml
+++ b/app/views/stream_entries/_og_description.html.haml
@@ -1,4 +1 @@
-- if activity.is_a?(Status) && activity.spoiler_text?
-  = opengraph 'og:description', activity.spoiler_text
-- else
-  = opengraph 'og:description', activity.content
+= opengraph 'og:description', [activity.spoiler_text, activity.text].reject(&:blank?).join("\n\n")
diff --git a/app/views/stream_entries/_og_image.html.haml b/app/views/stream_entries/_og_image.html.haml
index 1056c1744..526034faa 100644
--- a/app/views/stream_entries/_og_image.html.haml
+++ b/app/views/stream_entries/_og_image.html.haml
@@ -1,23 +1,34 @@
-- if activity.is_a?(Status) && activity.non_sensitive_with_media?
+- if activity.is_a?(Status) && activity.media_attachments.any?
+  - player_card = false
   - activity.media_attachments.each do |media|
     - if media.image?
       = opengraph 'og:image', full_asset_url(media.file.url(:original))
       = opengraph 'og:image:type', media.file_content_type
       - unless media.file.meta.nil?
-        = opengraph 'og:image:width', media.file.meta['original']['width']
-        = opengraph 'og:image:height', media.file.meta['original']['height']
-    - elsif media.video?
+        = opengraph 'og:image:width', media.file.meta.dig('original', 'width')
+        = opengraph 'og:image:height', media.file.meta.dig('original', 'height')
+    - elsif media.video? || media.gifv?
+      - player_card = true
       = opengraph 'og:image', full_asset_url(media.file.url(:small))
       = opengraph 'og:image:type', 'image/png'
       - unless media.file.meta.nil?
-        = opengraph 'og:image:width', media.file.meta['small']['width']
-        = opengraph 'og:image:height', media.file.meta['small']['height']
+        = opengraph 'og:image:width', media.file.meta.dig('small', 'width')
+        = opengraph 'og:image:height', media.file.meta.dig('small', 'height')
       = opengraph 'og:video', full_asset_url(media.file.url(:original))
+      = opengraph 'og:video:secure_url', full_asset_url(media.file.url(:original))
       = opengraph 'og:video:type', media.file_content_type
+      = opengraph 'twitter:player', medium_player_url(media)
+      = opengraph 'twitter:player:stream', full_asset_url(media.file.url(:original))
+      = opengraph 'twitter:player:stream:content_type', media.file_content_type
       - unless media.file.meta.nil?
-        = opengraph 'og:video:width', media.file.meta['small']['width']
-        = opengraph 'og:video:height', media.file.meta['small']['height']
-  = opengraph 'twitter:card', 'summary_large_image'
+        = opengraph 'og:video:width', media.file.meta.dig('original', 'width')
+        = opengraph 'og:video:height', media.file.meta.dig('original', 'height')
+        = opengraph 'twitter:player:width', media.file.meta.dig('original', 'width')
+        = opengraph 'twitter:player:height', media.file.meta.dig('original', 'height')
+  - if player_card
+    = opengraph 'twitter:card', 'player'
+  - else
+    = opengraph 'twitter:card', 'summary_large_image'
 - else
   = opengraph 'og:image', full_asset_url(account.avatar.url(:original))
   = opengraph 'og:image:width', '120'
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index 0b45ff308..2ad1f5120 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -20,10 +20,9 @@
         %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
     .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }<
       = Formatter.instance.format(status, custom_emojify: true)
-
       - unless status.media_attachments.empty?
         - if status.media_attachments.first.video?
           - video = status.media_attachments.first
-          %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 610, height: 343) }}><
+          %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343) }}
         - else
-          %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 343, sensitive: status.sensitive?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}><
+          %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}
diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml
index cf6671e67..a87c51952 100644
--- a/app/views/stream_entries/show.html.haml
+++ b/app/views/stream_entries/show.html.haml
@@ -1,5 +1,5 @@
 - content_for :page_title do
-  = t('statuses.title', name: display_name(@account), quote: truncate(@stream_entry.activity.spoiler_text.presence || @stream_entry.activity.text, length: 50, omission: '…'))
+  = t('statuses.title', name: display_name(@account), quote: truncate(@stream_entry.activity.spoiler_text.presence || @stream_entry.activity.text, length: 50, omission: '…', escape: false))
 
 - content_for :header_tags do
   - if @account.user&.setting_noindex
diff --git a/app/views/tags/_features.html.haml b/app/views/tags/_features.html.haml
new file mode 100644
index 000000000..8fbc6b760
--- /dev/null
+++ b/app/views/tags/_features.html.haml
@@ -0,0 +1,25 @@
+.features-list
+  .features-list__row
+    .text
+      %h6= t 'about.features.real_conversation_title'
+      = t 'about.features.real_conversation_body'
+    .visual
+      = fa_icon 'fw comments'
+  .features-list__row
+    .text
+      %h6= t 'about.features.not_a_product_title'
+      = t 'about.features.not_a_product_body'
+    .visual
+      = fa_icon 'fw users'
+  .features-list__row
+    .text
+      %h6= t 'about.features.within_reach_title'
+      = t 'about.features.within_reach_body'
+    .visual
+      = fa_icon 'fw mobile'
+  .features-list__row
+    .text
+      %h6= t 'about.features.humane_approach_title'
+      = t 'about.features.humane_approach_body'
+    .visual
+      = fa_icon 'fw leaf'
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
index 03f19e20a..000aa0c4d 100644
--- a/app/views/tags/show.html.haml
+++ b/app/views/tags/show.html.haml
@@ -5,48 +5,31 @@
   %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
   = render 'og'
 
-.landing-page.tag-page
+.landing-page.tag-page.alternative
   .features
     .container
-      #mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) } }
+      .grid
+        .column-1
+          #mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) } }
 
-      .about-mastodon
-        .about-hashtag
-          .brand
-            = link_to root_url do
-              = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
+        .column-2
+          .about-mastodon
+            .about-hashtag.landing-page__information
+              .brand
+                = link_to root_url do
+                  = image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
 
-          %p= t 'about.about_hashtag_html', hashtag: @tag.name
+              %p= t 'about.about_hashtag_html', hashtag: @tag.name
 
-          .cta
-            - if user_signed_in?
-              = link_to t('settings.back'), root_path, class: 'button button-secondary'
-            - else
-              = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary'
-            = link_to t('about.learn_more'), about_path, class: 'button button-alternative'
+              .cta
+                - if user_signed_in?
+                  = link_to t('settings.back'), root_path, class: 'button button-secondary'
+                - else
+                  = link_to t('auth.login'), new_user_session_path, class: 'button button-secondary'
+                = link_to t('about.learn_more'), about_path, class: 'button button-alternative'
 
-        .features-list
-          .features-list__row
-            .text
-              %h6= t 'about.features.real_conversation_title'
-              = t 'about.features.real_conversation_body'
-            .visual
-              = fa_icon 'fw comments'
-          .features-list__row
-            .text
-              %h6= t 'about.features.not_a_product_title'
-              = t 'about.features.not_a_product_body'
-            .visual
-              = fa_icon 'fw users'
-          .features-list__row
-            .text
-              %h6= t 'about.features.within_reach_title'
-              = t 'about.features.within_reach_body'
-            .visual
-              = fa_icon 'fw mobile'
-          .features-list__row
-            .text
-              %h6= t 'about.features.humane_approach_title'
-              = t 'about.features.humane_approach_body'
-            .visual
-              = fa_icon 'fw leaf'
+            .landing-page__features.landing-page__information
+              %h3= t 'about.what_is_mastodon'
+              %p= t 'about.about_mastodon_html'
+
+              = render 'features'
diff --git a/app/views/user_mailer/backup_ready.html.haml b/app/views/user_mailer/backup_ready.html.haml
new file mode 100644
index 000000000..d5a4b8b48
--- /dev/null
+++ b/app/views/user_mailer/backup_ready.html.haml
@@ -0,0 +1,59 @@
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.hero
+                  .email-row
+                    .col-6
+                      %table.column{ cellspacing: 0, cellpadding: 0 }
+                        %tbody
+                          %tr
+                            %td.column-cell.text-center.padded
+                              %table.hero-icon{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                                %tbody
+                                  %tr
+                                    %td
+                                      = image_tag full_pack_url('icon_file_download.png'), alt: ''
+
+                              %h1= t 'user_mailer.backup_ready.title'
+
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell.content-start
+                  .email-row
+                    .col-6
+                      %table.column{ cellspacing: 0, cellpadding: 0 }
+                        %tbody
+                          %tr
+                            %td.column-cell.text-center
+                              %p= t 'user_mailer.backup_ready.explanation'
+
+%table.email-table{ cellspacing: 0, cellpadding: 0 }
+  %tbody
+    %tr
+      %td.email-body
+        .email-container
+          %table.content-section{ cellspacing: 0, cellpadding: 0 }
+            %tbody
+              %tr
+                %td.content-cell
+                  %table.column{ cellspacing: 0, cellpadding: 0 }
+                    %tbody
+                      %tr
+                        %td.column-cell.button-cell
+                          %table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
+                            %tbody
+                              %tr
+                                %td.button-primary
+                                  = link_to full_asset_url(@backup.dump.url) do
+                                    %span= t 'exports.archive_takeout.download'
diff --git a/app/views/user_mailer/backup_ready.text.erb b/app/views/user_mailer/backup_ready.text.erb
new file mode 100644
index 000000000..eb89e7d74
--- /dev/null
+++ b/app/views/user_mailer/backup_ready.text.erb
@@ -0,0 +1,7 @@
+<%= t 'user_mailer.backup_ready.title' %>
+
+===
+
+<%= t 'user_mailer.backup_ready.explanation' %>
+
+=> <%= full_asset_url(@backup.dump.url) %>
diff --git a/app/workers/activitypub/synchronize_featured_collection_worker.rb b/app/workers/activitypub/synchronize_featured_collection_worker.rb
new file mode 100644
index 000000000..dd676a3ee
--- /dev/null
+++ b/app/workers/activitypub/synchronize_featured_collection_worker.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ActivityPub::SynchronizeFeaturedCollectionWorker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: 'pull'
+
+  def perform(account_id)
+    ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id))
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+end
diff --git a/app/workers/backup_worker.rb b/app/workers/backup_worker.rb
new file mode 100644
index 000000000..ec6db4e9e
--- /dev/null
+++ b/app/workers/backup_worker.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class BackupWorker
+  include Sidekiq::Worker
+
+  sidekiq_options queue: 'pull'
+
+  def perform(backup_id)
+    backup = Backup.find(backup_id)
+    user   = backup.user
+
+    BackupService.new.call(backup)
+
+    user.backups.where.not(id: backup.id).destroy_all
+    UserMailer.backup_ready(user, backup).deliver_later
+  end
+end
diff --git a/app/workers/import/relationship_worker.rb b/app/workers/import/relationship_worker.rb
index ed4c962c1..1dd8bf8fb 100644
--- a/app/workers/import/relationship_worker.rb
+++ b/app/workers/import/relationship_worker.rb
@@ -7,7 +7,7 @@ class Import::RelationshipWorker
 
   def perform(account_id, target_account_uri, relationship)
     from_account   = Account.find(account_id)
-    target_account = ResolveRemoteAccountService.new.call(target_account_uri)
+    target_account = ResolveAccountService.new.call(target_account_uri)
 
     return if target_account.nil?
 
diff --git a/app/workers/resolve_remote_account_worker.rb b/app/workers/resolve_account_worker.rb
index 5dd84ccb6..cd7c4d7dd 100644
--- a/app/workers/resolve_remote_account_worker.rb
+++ b/app/workers/resolve_account_worker.rb
@@ -1,11 +1,11 @@
 # frozen_string_literal: true
 
-class ResolveRemoteAccountWorker
+class ResolveAccountWorker
   include Sidekiq::Worker
 
   sidekiq_options queue: 'pull', unique: :until_executed
 
   def perform(uri)
-    ResolveRemoteAccountService.new.call(uri)
+    ResolveAccountService.new.call(uri)
   end
 end
diff --git a/app/workers/scheduler/backup_cleanup_scheduler.rb b/app/workers/scheduler/backup_cleanup_scheduler.rb
new file mode 100644
index 000000000..7a9d4f894
--- /dev/null
+++ b/app/workers/scheduler/backup_cleanup_scheduler.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+require 'sidekiq-scheduler'
+
+class Scheduler::BackupCleanupScheduler
+  include Sidekiq::Worker
+
+  def perform
+    old_backups.find_each(&:destroy!)
+  end
+
+  private
+
+  def old_backups
+    Backup.where('created_at < ?', 7.days.ago)
+  end
+end
diff --git a/boxfile.yml b/boxfile.yml
index 6b904e07d..bb4149e70 100644
--- a/boxfile.yml
+++ b/boxfile.yml
@@ -1,7 +1,7 @@
 run.config:
   engine: ruby
   engine.config:
-    runtime: ruby-2.4
+    runtime: ruby-2.5
 
   extra_packages:
     # basic servers:
@@ -10,6 +10,7 @@ run.config:
 
     # for images:
     - ImageMagick
+    - jemalloc
 
     # for videos:
     - ffmpeg3
@@ -37,7 +38,7 @@ run.config:
     - yarn.lock
 
   extra_steps:
-    - envsubst < .env.nanobox > .env
+    - cp .env.nanobox .env
     - yarn
 
   fs_watch: true
@@ -47,7 +48,7 @@ deploy.config:
   extra_steps:
     - NODE_ENV=production bundle exec rake assets:precompile
   transform:
-    - "sed 's/LOCAL_HTTPS=.*/LOCAL_HTTPS=true/i' /app/.env.nanobox | envsubst > /app/.env.production"
+    - "envsubst < /app/.env.nanobox > /app/.env.production"
     - |-
         if [ -z "$LOCAL_DOMAIN" ]
         then
@@ -186,7 +187,7 @@ worker.cron_only:
 
 
 data.db:
-  image: nanobox/postgresql:9.5
+  image: nanobox/postgresql:9.6
 
   cron:
     - id: backup
@@ -196,11 +197,11 @@ data.db:
         gzip |
         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).sql.gz --data-binary @- &&
         curl -k -s -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/ |
-        json_pp |
+        sed 's/,/\n/g' |
         grep ${HOSTNAME} |
         sort |
         head -n-${BACKUP_COUNT:-1} |
-        sed 's/.*: "\(.*\)".*/\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
@@ -208,7 +209,7 @@ data.db:
 
 
 data.redis:
-  image: nanobox/redis:3.0
+  image: nanobox/redis:4.0
 
   cron:
     - id: backup
@@ -216,11 +217,11 @@ data.redis:
       command: |
         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).rdb --data-binary @/data/var/db/redis/dump.rdb &&
         curl -k -s -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/ |
-        json_pp |
+        sed 's/,/\n/g' |
         grep ${HOSTNAME} |
         sort |
         head -n-${BACKUP_COUNT:-1} |
-        sed 's/.*: "\(.*\)".*/\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
@@ -234,14 +235,14 @@ data.storage:
     - id: backup
       schedule: '0 3 * * *'
       command: |
-        tar cz -C /data/var/db/unfs/ |
+        tar cz -C /data/var/db/unfs/ . |
         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 --data-binary @- &&
         curl -k -s -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/ |
-        json_pp |
+        sed 's/,/\n/g' |
         grep ${HOSTNAME} |
         sort |
         head -n-${BACKUP_COUNT:-1} |
-        sed 's/.*: "\(.*\)".*/\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
diff --git a/config/application.rb b/config/application.rb
index ef97dbc06..f63746e34 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -7,11 +7,13 @@ require 'rails/all'
 Bundler.require(*Rails.groups)
 
 require_relative '../app/lib/exceptions'
+require_relative '../lib/paperclip/lazy_thumbnail'
 require_relative '../lib/paperclip/gif_transcoder'
 require_relative '../lib/paperclip/video_transcoder'
 require_relative '../lib/paperclip/audio_transcoder'
 require_relative '../lib/mastodon/snowflake'
 require_relative '../lib/mastodon/version'
+require_relative '../lib/devise/ldap_authenticatable'
 
 Dotenv::Railtie.load
 
@@ -30,7 +32,7 @@ module Mastodon
     # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
     # config.time_zone = 'Central Time (US & Canada)'
 
-    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
+    # All translations from config/locales/*.rb,yml are auto loaded.
     # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
     config.i18n.available_locales = [
       :en,
@@ -47,6 +49,7 @@ module Mastodon
       :he,
       :hr,
       :hu,
+      :hy,
       :id,
       :io,
       :it,
@@ -71,7 +74,12 @@ module Mastodon
       :'zh-TW',
     ]
 
-    config.i18n.default_locale = :en
+    config.i18n.default_locale = ENV['DEFAULT_LOCALE']&.to_sym
+    if config.i18n.available_locales.include?(config.i18n.default_locale)
+      config.i18n.fallbacks = [:en]
+    else
+      config.i18n.default_locale = :en
+    end
 
     # config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
     # config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index db7e37bb9..e8956639c 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -7,7 +7,7 @@
       "check_name": "LinkToHref",
       "message": "Potentially unsafe model attribute in link_to href",
       "file": "app/views/admin/accounts/show.html.haml",
-      "line": 143,
+      "line": 147,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).inbox_url, Account.find(params[:id]).inbox_url)",
       "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}],
@@ -26,7 +26,7 @@
       "check_name": "LinkToHref",
       "message": "Potentially unsafe model attribute in link_to href",
       "file": "app/views/admin/accounts/show.html.haml",
-      "line": 149,
+      "line": 153,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).shared_inbox_url, Account.find(params[:id]).shared_inbox_url)",
       "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}],
@@ -45,7 +45,7 @@
       "check_name": "LinkToHref",
       "message": "Potentially unsafe model attribute in link_to href",
       "file": "app/views/admin/accounts/show.html.haml",
-      "line": 54,
+      "line": 57,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).url, Account.find(params[:id]).url)",
       "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}],
@@ -67,7 +67,7 @@
       "line": 3,
       "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
       "code": "render(action => \"stream_entries/#{Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase}\", { Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase.to_sym => Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity, :centered => true })",
-      "render_path": [{"type":"controller","class":"StatusesController","method":"embed","line":41,"file":"app/controllers/statuses_controller.rb"}],
+      "render_path": [{"type":"controller","class":"StatusesController","method":"embed","line":45,"file":"app/controllers/statuses_controller.rb"}],
       "location": {
         "type": "template",
         "template": "stream_entries/embed"
@@ -102,7 +102,7 @@
       "check_name": "LinkToHref",
       "message": "Potentially unsafe model attribute in link_to href",
       "file": "app/views/admin/accounts/show.html.haml",
-      "line": 152,
+      "line": 156,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).followers_url, Account.find(params[:id]).followers_url)",
       "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}],
@@ -121,7 +121,7 @@
       "check_name": "LinkToHref",
       "message": "Potentially unsafe model attribute in link_to href",
       "file": "app/views/admin/accounts/show.html.haml",
-      "line": 127,
+      "line": 130,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).salmon_url, Account.find(params[:id]).salmon_url)",
       "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}],
@@ -140,10 +140,10 @@
       "check_name": "Render",
       "message": "Render path contains parameter value",
       "file": "app/views/admin/custom_emojis/index.html.haml",
-      "line": 31,
+      "line": 45,
       "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
       "code": "render(action => filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]), {})",
-      "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":10,"file":"app/controllers/admin/custom_emojis_controller.rb"}],
+      "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":11,"file":"app/controllers/admin/custom_emojis_controller.rb"}],
       "location": {
         "type": "template",
         "template": "admin/custom_emojis/index"
@@ -179,7 +179,7 @@
       "check_name": "Render",
       "message": "Render path contains parameter value",
       "file": "app/views/admin/accounts/index.html.haml",
-      "line": 64,
+      "line": 67,
       "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
       "code": "render(action => filtered_accounts.page(params[:page]), {})",
       "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"index","line":12,"file":"app/controllers/admin/accounts_controller.rb"}],
@@ -192,13 +192,52 @@
       "note": ""
     },
     {
+      "warning_type": "Cross-Site Request Forgery",
+      "warning_code": 7,
+      "fingerprint": "ab491f72606337a348482d006eb67a3b1616685fd48644d5ac909bbcd62a5000",
+      "check_name": "ForgerySetting",
+      "message": "'protect_from_forgery' should be called in WellKnown::HostMetaController",
+      "file": "app/controllers/well_known/host_meta_controller.rb",
+      "line": 4,
+      "link": "http://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/",
+      "code": null,
+      "render_path": null,
+      "location": {
+        "type": "controller",
+        "controller": "WellKnown::HostMetaController"
+      },
+      "user_input": null,
+      "confidence": "High",
+      "note": ""
+    },
+    {
+      "warning_type": "Redirect",
+      "warning_code": 18,
+      "fingerprint": "ba699ddcc6552c422c4ecd50d2cd217f616a2446659e185a50b05a0f2dad8d33",
+      "check_name": "Redirect",
+      "message": "Possible unprotected redirect",
+      "file": "app/controllers/media_controller.rb",
+      "line": 10,
+      "link": "http://brakemanscanner.org/docs/warning_types/redirect/",
+      "code": "redirect_to(MediaAttachment.attached.find_by!(:shortcode => ((params[:id] or params[:medium_id]))).file.url(:original))",
+      "render_path": null,
+      "location": {
+        "type": "method",
+        "class": "MediaController",
+        "method": "show"
+      },
+      "user_input": "MediaAttachment.attached.find_by!(:shortcode => ((params[:id] or params[:medium_id]))).file.url(:original)",
+      "confidence": "High",
+      "note": ""
+    },
+    {
       "warning_type": "Cross-Site Scripting",
       "warning_code": 4,
       "fingerprint": "bb0ad5c4a42e06e3846c2089ff5269c17f65483a69414f6ce65eecf2bb11fab7",
       "check_name": "LinkToHref",
       "message": "Potentially unsafe model attribute in link_to href",
       "file": "app/views/admin/accounts/show.html.haml",
-      "line": 116,
+      "line": 119,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).remote_url, Account.find(params[:id]).remote_url)",
       "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}],
@@ -250,13 +289,32 @@
       "note": ""
     },
     {
+      "warning_type": "Cross-Site Request Forgery",
+      "warning_code": 7,
+      "fingerprint": "d4278f04e807ec58a23925f8ab31fad5e84692f2fb9f2f57e7931aff05d57cf8",
+      "check_name": "ForgerySetting",
+      "message": "'protect_from_forgery' should be called in WellKnown::WebfingerController",
+      "file": "app/controllers/well_known/webfinger_controller.rb",
+      "line": 4,
+      "link": "http://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/",
+      "code": null,
+      "render_path": null,
+      "location": {
+        "type": "controller",
+        "controller": "WellKnown::WebfingerController"
+      },
+      "user_input": null,
+      "confidence": "High",
+      "note": ""
+    },
+    {
       "warning_type": "Cross-Site Scripting",
       "warning_code": 4,
       "fingerprint": "e04aafe1e06cf8317fb6ac0a7f35783e45aa1274272ee6eaf28d39adfdad489b",
       "check_name": "LinkToHref",
       "message": "Potentially unsafe model attribute in link_to href",
       "file": "app/views/admin/accounts/show.html.haml",
-      "line": 146,
+      "line": 150,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).outbox_url, Account.find(params[:id]).outbox_url)",
       "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":18,"file":"app/controllers/admin/accounts_controller.rb"}],
@@ -275,10 +333,10 @@
       "check_name": "Render",
       "message": "Render path contains parameter value",
       "file": "app/views/stream_entries/show.html.haml",
-      "line": 21,
+      "line": 24,
       "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
       "code": "render(partial => \"stream_entries/#{Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase}\", { :locals => ({ Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity_type.downcase.to_sym => Account.find_local!(params[:account_username]).statuses.find(params[:id]).stream_entry.activity, :include_threads => true }) })",
-      "render_path": [{"type":"controller","class":"StatusesController","method":"show","line":20,"file":"app/controllers/statuses_controller.rb"}],
+      "render_path": [{"type":"controller","class":"StatusesController","method":"show","line":22,"file":"app/controllers/statuses_controller.rb"}],
       "location": {
         "type": "template",
         "template": "stream_entries/show"
@@ -288,6 +346,6 @@
       "note": ""
     }
   ],
-  "updated": "2017-11-19 20:34:18 +0100",
+  "updated": "2018-02-16 06:42:53 +0100",
   "brakeman_version": "4.0.1"
 }
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 59bc2c3e2..285fea8b8 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -82,6 +82,14 @@ Rails.application.configure do
 
     Bullet.add_whitelist type: :n_plus_one_query, class_name: 'User', association: :account
   end
+
+  config.x.otp_secret = ENV.fetch('OTP_SECRET', '1fc2b87989afa6351912abeebe31ffc5c476ead9bf8b3d74cbc4a302c7b69a45b40b1bbef3506ddad73e942e15ed5ca4b402bf9a66423626051104f4b5f05109')
 end
 
 ActiveRecordQueryTrace.enabled = ENV.fetch('QUERY_TRACE_ENABLED') { false }
+
+module PrivateAddressCheck
+  def self.private_address?(*)
+    false
+  end
+end
diff --git a/config/environments/production.rb b/config/environments/production.rb
index a691ef873..6cd13d441 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -25,7 +25,7 @@ Rails.application.configure do
   end
 
   # Compress JavaScripts and CSS.
-  config.assets.js_compressor = Uglifier.new(mangle: false)
+  # config.assets.js_compressor = Uglifier.new(mangle: false)
   # config.assets.css_compressor = :sass
 
   # Do not fallback to assets pipeline if a precompiled asset is missed.
@@ -96,9 +96,11 @@ Rails.application.configure do
     'X-Content-Type-Options'  => 'nosniff',
     'X-XSS-Protection'        => '1; mode=block',
     'Content-Security-Policy' => "frame-ancestors 'none'; object-src 'none'; script-src 'self' https://dev-static.glitch.social ; base-uri 'none';" , 
-    'Referrer-Policy'         => 'no-referrer, strict-origin-when-cross-origin',
+    'Referrer-Policy'         => 'same-origin',
     'Strict-Transport-Security' => 'max-age=63072000; includeSubDomains; preload',
     'X-Clacks-Overhead' => 'GNU Natalie Nguyen'
 
   }
+
+  config.x.otp_secret = ENV.fetch('OTP_SECRET')
 end
diff --git a/config/environments/test.rb b/config/environments/test.rb
index e68cb156d..7d77a170e 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -23,6 +23,10 @@ Rails.application.configure do
   config.consider_all_requests_local       = true
   config.action_controller.perform_caching = false
 
+  # The default store, file_store is shared by processses parallely executed
+  # and should not be used.
+  config.cache_store = :memory_store
+
   # Raise exceptions instead of rendering exception templates.
   config.action_dispatch.show_exceptions = false
 
@@ -40,6 +44,8 @@ Rails.application.configure do
   # Print deprecation notices to the stderr.
   config.active_support.deprecation = :stderr
 
+  config.x.otp_secret = '100c7faeef00caa29242f6b04156742bf76065771fd4117990c4282b8748ff3d99f8fdae97c982ab5bd2e6756a159121377cce4421f4a8ecd2d67bd7749a3fb4'
+
   # Generate random VAPID keys
   vapid_key = Webpush.generate_key
   config.x.vapid_private_key = vapid_key.private_key
@@ -47,6 +53,9 @@ Rails.application.configure do
 
   # Raises error for missing translations
   # config.action_view.raise_on_missing_translations = true
+
+  config.i18n.default_locale = :en
+  config.i18n.fallbacks = true
 end
 
 Paperclip::Attachment.default_options[:path] = "#{Rails.root}/spec/test_files/:class/:id_partition/:style.:extension"
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index 9e1b620f5..62e78556f 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -46,6 +46,7 @@ ignore_missing:
   - 'terms.body_html'
   - 'application_mailer.salutation'
   - 'errors.500'
+  - 'auth.providers.*'
 
 ignore_unused:
   - 'activemodel.errors.*'
diff --git a/config/initializers/chewy.rb b/config/initializers/chewy.rb
new file mode 100644
index 000000000..d5347f2bf
--- /dev/null
+++ b/config/initializers/chewy.rb
@@ -0,0 +1,24 @@
+enabled         = ENV['ES_ENABLED'] == 'true'
+host            = ENV.fetch('ES_HOST') { 'localhost' }
+port            = ENV.fetch('ES_PORT') { 9200 }
+fallback_prefix = ENV.fetch('REDIS_NAMESPACE') { nil }
+prefix          = ENV.fetch('ES_PREFIX') { fallback_prefix }
+
+Chewy.settings = {
+  host: "#{host}:#{port}",
+  prefix: prefix,
+  enabled: enabled,
+  journal: false,
+  sidekiq: { queue: 'pull' },
+}
+
+Chewy.root_strategy    = enabled ? :sidekiq : :bypass
+Chewy.request_strategy = enabled ? :sidekiq : :bypass
+
+module Chewy
+  class << self
+    def enabled?
+      settings[:enabled]
+    end
+  end
+end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 07912c28b..df45dcd1f 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -30,8 +30,43 @@ Warden::Manager.before_logout do |_, warden|
   warden.cookies.delete('_session_id')
 end
 
+module Devise
+  mattr_accessor :pam_authentication
+  @@pam_authentication = false
+  mattr_accessor :pam_controlled_service
+  @@pam_controlled_service = nil
+
+  mattr_accessor :check_at_sign
+  @@check_at_sign = false
+
+  mattr_accessor :ldap_authentication
+  @@ldap_authentication = false
+  mattr_accessor :ldap_host
+  @@ldap_host = nil
+  mattr_accessor :ldap_port
+  @@ldap_port = nil
+  mattr_accessor :ldap_method
+  @@ldap_method = nil
+  mattr_accessor :ldap_base
+  @@ldap_base = nil
+  mattr_accessor :ldap_uid
+  @@ldap_uid = nil
+  mattr_accessor :ldap_bind_dn
+  @@ldap_bind_dn = nil
+  mattr_accessor :ldap_password
+  @@ldap_password = nil
+
+  class Strategies::PamAuthenticatable
+    def valid?
+      super && ::Devise.pam_authentication
+    end
+  end
+end
+
 Devise.setup do |config|
   config.warden do |manager|
+    manager.default_strategies(scope: :user).unshift :ldap_authenticatable if Devise.ldap_authentication
+    manager.default_strategies(scope: :user).unshift :pam_authenticatable  if Devise.pam_authentication
     manager.default_strategies(scope: :user).unshift :two_factor_authenticatable
     manager.default_strategies(scope: :user).unshift :two_factor_backupable
   end
@@ -96,7 +131,7 @@ Devise.setup do |config|
   # given strategies, for example, `config.http_authenticatable = [:database]` will
   # enable it only for database authentication. The supported strategies are:
   # :database      = Support basic authentication with authentication key + password
-  config.http_authenticatable = [:database]
+  config.http_authenticatable = [:pam, :database]
 
   # If 401 status code should be returned for AJAX requests. True by default.
   # config.http_authenticatable_on_xhr = true
@@ -301,4 +336,26 @@ Devise.setup do |config|
   # When using OmniAuth, Devise cannot automatically set OmniAuth path,
   # so you need to do it manually. For the users scope, it would be:
   # config.omniauth_path_prefix = '/my_engine/users/auth'
+
+  if ENV['PAM_ENABLED'] == 'true'
+    config.pam_authentication     = true
+    config.usernamefield          = nil
+    config.emailfield             = 'email'
+    config.check_at_sign          = true
+    config.pam_default_suffix     = ENV.fetch('PAM_DEFAULT_SUFFIX') { nil }
+    config.pam_default_service    = ENV.fetch('PAM_DEFAULT_SERVICE') { 'rpam' }
+    config.pam_controlled_service = ENV.fetch('PAM_CONTROLLED_SERVICE') { nil }
+  end
+
+  if ENV['LDAP_ENABLED'] == 'true'
+    config.ldap_authentication = true
+    config.check_at_sign       = true
+    config.ldap_host           = ENV.fetch('LDAP_HOST', 'localhost')
+    config.ldap_port           = ENV.fetch('LDAP_PORT', 389).to_i
+    config.ldap_method         = ENV.fetch('LDAP_METHOD', :simple_tls).to_sym
+    config.ldap_base           = ENV.fetch('LDAP_BASE')
+    config.ldap_bind_dn        = ENV.fetch('LDAP_BIND_DN')
+    config.ldap_password       = ENV.fetch('LDAP_PASSWORD')
+    config.ldap_uid            = ENV.fetch('LDAP_UID', 'cn')
+  end
 end
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
new file mode 100644
index 000000000..85fb81250
--- /dev/null
+++ b/config/initializers/omniauth.rb
@@ -0,0 +1,66 @@
+Rails.application.config.middleware.use OmniAuth::Builder do
+  # Vanilla omniauth stategies
+end
+
+Devise.setup do |config|
+  # Devise omniauth strategies
+  options = {}
+  options[:redirect_at_sign_in] = ENV['OAUTH_REDIRECT_AT_SIGN_IN'] == 'true'
+
+  # CAS strategy
+  if ENV['CAS_ENABLED'] == 'true'
+    cas_options = options
+    cas_options[:url] = ENV['CAS_URL'] if ENV['CAS_URL']
+    cas_options[:host] = ENV['CAS_HOST'] if ENV['CAS_HOST']
+    cas_options[:port] = ENV['CAS_PORT'] if ENV['CAS_PORT']
+    cas_options[:ssl] = ENV['CAS_SSL'] == 'true' if ENV['CAS_SSL']
+    cas_options[:validate_url] = ENV['CAS_VALIDATE_URL'] if ENV['CAS_VALIDATE_URL']
+    cas_options[:callback_url] = ENV['CAS_CALLBACK_URL'] if ENV['CAS_CALLBACK_URL']
+    cas_options[:logout_url] = ENV['CAS_LOGOUT_URL'] if ENV['CAS_LOGOUT_URL']
+    cas_options[:login_url] = ENV['CAS_LOGIN_URL'] if ENV['CAS_LOGIN_URL']
+    cas_options[:uid_field] = ENV['CAS_UID_FIELD'] || 'user' if ENV['CAS_UID_FIELD']
+    cas_options[:ca_path] = ENV['CAS_CA_PATH'] if ENV['CAS_CA_PATH']
+    cas_options[:disable_ssl_verification] = ENV['CAS_DISABLE_SSL_VERIFICATION'] == 'true'
+    cas_options[:uid_key] = ENV['CAS_UID_KEY'] || 'user'
+    cas_options[:name_key] = ENV['CAS_NAME_KEY'] || 'name'
+    cas_options[:email_key] = ENV['CAS_EMAIL_KEY'] || 'email'
+    cas_options[:nickname_key] = ENV['CAS_NICKNAME_KEY'] || 'nickname'
+    cas_options[:first_name_key] = ENV['CAS_FIRST_NAME_KEY'] || 'firstname'
+    cas_options[:last_name_key] = ENV['CAS_LAST_NAME_KEY'] || 'lastname'
+    cas_options[:location_key] = ENV['CAS_LOCATION_KEY'] || 'location'
+    cas_options[:image_key] = ENV['CAS_IMAGE_KEY'] || 'image'
+    cas_options[:phone_key] = ENV['CAS_PHONE_KEY'] || 'phone'
+    config.omniauth :cas, cas_options
+  end
+
+  # SAML strategy
+  if ENV['SAML_ENABLED'] == 'true'
+    saml_options = options
+    saml_options[:assertion_consumer_service_url] = ENV['SAML_ACS_URL'] if ENV['SAML_ACS_URL']
+    saml_options[:issuer] = ENV['SAML_ISSUER'] if ENV['SAML_ISSUER']
+    saml_options[:idp_sso_target_url] = ENV['SAML_IDP_SSO_TARGET_URL']  if ENV['SAML_IDP_SSO_TARGET_URL']
+    saml_options[:idp_sso_target_url_runtime_params] = ENV['SAML_IDP_SSO_TARGET_PARAMS'] if ENV['SAML_IDP_SSO_TARGET_PARAMS'] # FIXME: Should be parsable Hash
+    saml_options[:idp_cert] = ENV['SAML_IDP_CERT'] if ENV['SAML_IDP_CERT']
+    saml_options[:idp_cert_fingerprint] = ENV['SAML_IDP_CERT_FINGERPRINT'] if ENV['SAML_IDP_CERT_FINGERPRINT']
+    saml_options[:idp_cert_fingerprint_validator] = ENV['SAML_IDP_CERT_FINGERPRINT_VALIDATOR'] if ENV['SAML_IDP_CERT_FINGERPRINT_VALIDATOR'] # FIXME: Should be Lambda { |fingerprint| }
+    saml_options[:name_identifier_format] = ENV['SAML_NAME_IDENTIFIER_FORMAT'] if ENV['SAML_NAME_IDENTIFIER_FORMAT']
+    saml_options[:request_attributes] = {}
+    saml_options[:certificate] = ENV['SAML_CERT'] if ENV['SAML_CERT']
+    saml_options[:private_key] = ENV['SAML_PRIVATE_KEY'] if ENV['SAML_PRIVATE_KEY']
+    saml_options[:security] = {}
+    saml_options[:security][:want_assertions_signed] = ENV['SAML_SECURITY_WANT_ASSERTION_SIGNED'] == 'true'
+    saml_options[:security][:want_assertions_encrypted] = ENV['SAML_SECURITY_WANT_ASSERTION_ENCRYPTED'] == 'true'
+    saml_options[:security][:assume_email_is_verified] = ENV['SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED'] == 'true'
+    saml_options[:attribute_statements] = {}
+    saml_options[:attribute_statements][:uid] = [ENV['SAML_ATTRIBUTES_STATEMENTS_UID']] if ENV['SAML_ATTRIBUTES_STATEMENTS_UID']
+    saml_options[:attribute_statements][:email] = [ENV['SAML_ATTRIBUTES_STATEMENTS_EMAIL']] if ENV['SAML_ATTRIBUTES_STATEMENTS_EMAIL']
+    saml_options[:attribute_statements][:full_name] = [ENV['SAML_ATTRIBUTES_STATEMENTS_FULL_NAME']] if ENV['SAML_ATTRIBUTES_STATEMENTS_FULL_NAME']
+    saml_options[:attribute_statements][:first_name] = [ENV['SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME']] if ENV['SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME']
+    saml_options[:attribute_statements][:last_name] = [ENV['SAML_ATTRIBUTES_STATEMENTS_LAST_NAME']] if ENV['SAML_ATTRIBUTES_STATEMENTS_LAST_NAME']
+    saml_options[:attribute_statements][:verified] = [ENV['SAML_ATTRIBUTES_STATEMENTS_VERIFIED']] if ENV['SAML_ATTRIBUTES_STATEMENTS_VERIFIED']
+    saml_options[:attribute_statements][:verified_email] = [ENV['SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL']] if ENV['SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL']
+    saml_options[:uid_attribute] = ENV['SAML_UID_ATTRIBUTE'] if ENV['SAML_UID_ATTRIBUTE']
+    config.omniauth :saml, saml_options
+  end
+
+end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index b70784d79..f875fbd95 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -9,6 +9,10 @@ end
 
 Sidekiq.configure_server do |config|
   config.redis = redis_params
+
+  config.server_middleware do |chain|
+    chain.add SidekiqErrorHandler
+  end
 end
 
 Sidekiq.configure_client do |config|
diff --git a/config/initializers/twitter_regex.rb b/config/initializers/twitter_regex.rb
index e924fac22..7fa828300 100644
--- a/config/initializers/twitter_regex.rb
+++ b/config/initializers/twitter_regex.rb
@@ -2,7 +2,7 @@ module Twitter
   class Regex
 
     REGEXEN[:valid_general_url_path_chars] = /[^\p{White_Space}\(\)\?]/iou
-    REGEXEN[:valid_url_path_ending_chars] = /[^\p{White_Space}\(\)\?!\*';:=\,\.\$%\[\]\p{Pd}~&\|@]|(?:#{REGEXEN[:valid_url_balanced_parens]})/iou
+    REGEXEN[:valid_url_path_ending_chars] = /[^\p{White_Space}\(\)\?!\*';:=\,\.\$%\[\]~&\|@]|(?:#{REGEXEN[:valid_url_balanced_parens]})/iou
     REGEXEN[:valid_url_balanced_parens] = /
       \(
         (?:
diff --git a/config/locales/activerecord.ar.yml b/config/locales/activerecord.ar.yml
index d5d44aaa6..68c7fe939 100644
--- a/config/locales/activerecord.ar.yml
+++ b/config/locales/activerecord.ar.yml
@@ -6,8 +6,8 @@ ar:
         account:
           attributes:
             username:
-              invalid: فقط حروف و أرقام و تسطير سفلي
+              invalid: فقط حروف و أرقام و سطور سفلية
         status:
           attributes:
             reblog:
-              taken: المنشور موجود
+              taken: المنشور موجود مِن قبل
diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml
new file mode 100644
index 000000000..d18b16834
--- /dev/null
+++ b/config/locales/activerecord.es.yml
@@ -0,0 +1,13 @@
+---
+es:
+  activerecord:
+    errors:
+      models:
+        account:
+          attributes:
+            username:
+              invalid: solo letras, números y guiones bajos
+        status:
+          attributes:
+            reblog:
+              taken: del estado ya existe
diff --git a/config/locales/ar.yml b/config/locales/ar.yml
index eadeaef3e..41d83e4ad 100644
--- a/config/locales/ar.yml
+++ b/config/locales/ar.yml
@@ -12,9 +12,9 @@ ar:
     domain_count_before: متصل بـ
     features:
       humane_approach_title: أسلوب يعيد الإعتبار للإنسان
+      not_a_product_body: ماستدون ليس شبكة تجارية. لا يحتوي على إعلانات و لا يقوم باستغلال البيانات و لا هو بِبُستان مُسيَّج. لا تحكم فيه وليس له أية هيئةٍ مركزيةٍ.
       not_a_product_title: إنك إنسان و لست سلعة
       real_conversation_title: مبني لتحقيق تواصل حقيقي
-    find_another_instance: إبحث عن مثيل خادوم آخر
     generic_description: "%{domain} هو سيرفر من بين سيرفرات الشبكة"
     hosted_on: ماستدون مُستضاف على %{domain}
     learn_more: تعلم المزيد
@@ -30,6 +30,7 @@ ar:
     followers: متابِعون
     following: يتابعون
     media: الوسائط
+    moved_html: "%{name} إنتقلَ إلى %{new_profile_link} :"
     nothing_here: لا يوجد أي شيء هنا !
     people_followed_by: الأشخاص الذين يتبعهم %{name}
     people_who_follow: الأشخاص الذين يتبعون %{name}
@@ -44,7 +45,9 @@ ar:
   admin:
     account_moderation_notes:
       account: مُشرِف
+      create: إنشاء
       created_at: التاريخ
+      created_msg: تم إنشاء ملاحظة الإشراف بنجاح !
       delete: حذف
     accounts:
       are_you_sure: متأكد ؟
@@ -60,40 +63,80 @@ ar:
       email: البريد الإلكتروني
       enable: تفعيل
       enabled: مفعَّل
+      feed_url: عنوان رابط التغذية
       followers: المتابِعون
+      followers_url: عنوان رابط المتابِعين
       follows: يتابع
       ip: عنوان الإيبي
       location:
         all: الكل
+        local: المحلي
+        remote: عن بُعد
         title: الموقع
       media_attachments: الوسائط المرفقة
       moderation:
         all: الكل
+        silenced: تم كتمه
+        suspended: مُجَمَّد
+        title: الإشراف
+      moderation_notes: ملاحظات الإشراف
+      most_recent_activity: آخر نشاط حديث
       most_recent_ip: أحدث عنوان إيبي
       order:
+        alphabetic: أبجديًا
         most_recent: الأحدث
         title: الترتيب
       profile_url: رابط الملف الشخصي
       protocol: البروتوكول
+      public: عمومي
+      redownload: تحديث الصورة الرمزية
+      reset: إعادة التعيين
+      reset_password: إعادة ضبط كلمة السر
       role: التصريحات
       roles:
         admin: مدير
         moderator: مشرف
         staff: الفريق
         user: مستخدِم
+      salmon_url: عنوان رابط سالمون Salmon
       search: البحث
       statuses: المنشورات
       title: الحسابات
       username: إسم المستخدم
       web: الويب
+    action_logs:
+      actions:
+        create_custom_emoji: "%{name} قام برفع إيموجي جديد %{target}"
+        create_domain_block: "%{name} قام بحجب نطاق %{target}"
+        destroy_domain_block: "%{name} قام بإلغاء الحجب عن النطاق %{target}"
+        disable_custom_emoji: "%{name} قام بتعطيل الإيموجي %{target}"
+        enable_custom_emoji: "%{name} قام بتنشيط الإيموجي %{target}"
+        promote_user: "%{name} قام بترقية المستخدم %{target}"
+        update_custom_emoji: "%{name} قام بتحديث الإيموجي %{target}"
+      title: سِجلّ التفتيش و المعاينة
     custom_emojis:
+      by_domain: النطاق
       copy: نسخ
+      created_msg: تم إنشاء الإيموجي بنجاح !
       delete: حذف
+      disable: تعطيل
       emoji: إيموجي
       enable: تفعيل
+      image_hint: ملف PNG إلى غاية حجم 50 ك.ب
+      shortcode_hint: على الأقل حرفين، و فقط رموز أبجدية عددية و أسطر سفلية
+      title: الإيموجي الخاصة
       upload: رفع
     domain_blocks:
+      add_new: إضافة نطاق جديد
       domain: النطاق
+      new:
+        create: إنشاء حظر
+        severity:
+          noop: لا شيء
+          silence: كتم
+        title: حجب نطاق جديد
+      severities:
+        noop: لا شيء
       show:
         undo: إلغاء
       undo: إلغاء
@@ -103,13 +146,26 @@ ar:
       new:
         create: إضافة نطاق
     instances:
+      account_count: الحسابات المعروفة
       domain_name: النطاق
+      reset: إعادة تعيين
       search: البحث
+      title: مثيلات الخوادم المعروفة
+    invites:
+      filter:
+        all: الكل
+        available: المتوفرة
+        expired: المنتهي صلاحيتها
+      title: الدعوات
     reports:
       are_you_sure: هل أنت متأكد ؟
       comment:
         label: تعليق
+        none: لا شيء
       delete: حذف
+      id: معرّف ID
+      nsfw:
+        'true': إخفاء الوسائط المرفقة
       report_contents: المحتويات
       reported_by: أبلغ عنه من طرف
       status: الحالة
@@ -119,49 +175,77 @@ ar:
       contact_information:
         email: البريد الإلكتروني المهني
       registrations:
+        closed_message:
+          title: رسالة التسجيلات المقفلة
         deletion:
           desc_html: السماح لأي مستخدم إغلاق حسابه
         open:
+          desc_html: السماح للجميع بإنشاء حساب
           title: فتح التسجيل
+      site_description:
+        title: وصف مثيل الخادوم
       site_terms:
         title: شروط الخدمة المخصصة
       site_title: إسم مثيل الخادم
+      thumbnail:
+        title: الصورة الرمزية المصغرة لمثيل الخادوم
+      timeline_preview:
+        desc_html: عرض الخيط العمومي على صفحة الإستقبال
       title: إعدادات الموقع
     statuses:
       back_to_account: العودة إلى صفحة الحساب
       batch:
         delete: حذف
       media:
+        hide: إخفاء الوسائط
+        show: إظهار الوسائط
         title: الوسائط
+      title: منشورات الحساب
+      with_media: بالوسائط
     subscriptions:
       confirmed: مؤكَّد
+      expires_in: تنتهي مدة صلاحيتها في
       topic: الموضوع
     title: الإدارة
   application_mailer:
+    notification_preferences: تعديل خيارات البريد الإلكتروني
     salutation: "%{name}،"
     settings: 'تغيير تفضيلات البريد الإلكتروني : %{link}'
     view: 'View:'
+    view_profile: عرض الملف الشخصي
+    view_status: عرض المنشور
   applications:
     created: تم إنشاء التطبيق بنجاح
     destroyed: تم حذف التطبيق بنجاح
     invalid_url: إن الرابط المقدم غير صالح
+    regenerate_token: إعادة توليد رمز النفاذ
+    your_token: رمز نفاذك
   auth:
-    change_password: الهوية
+    confirm_email: تأكيد عنوان البريد الإلكتروني
     delete_account: حذف حساب
+    delete_account_html: إن كنت ترغب في حذف حسابك يُمكنك <a href="%{path}">المواصلة هنا</a>. سوف يُطلَبُ منك التأكيد قبل الحذف.
     didnt_get_confirmation: لم تتلق تعليمات التأكيد ؟
     forgot_password: نسيت كلمة المرور ؟
     login: تسجيل الدخول
     logout: خروج
     migrate_account: الإنتقال إلى حساب آخر
+    migrate_account_html: إن كنت ترغب في تحويل هذا الحساب نحو حساب آخَر، يُمكِنُك <a href="%{path}">إعداده هنا</a>.
+    or_log_in_with: أو قم بتسجيل الدخول بواسطة
+    providers:
+      cas: CAS
+      saml: SAML
     register: إنشاء حساب
     resend_confirmation: إعادة إرسال تعليمات التأكيد
     reset_password: إعادة تعيين كلمة المرور
+    security: الهوية
     set_new_password: تعيين كلمة مرور جديدة
   authorize_follow:
     error: يا للأسف، وقع هناك خطأ إثر عملية البحث عن الحساب عن بعد
     follow: إتبع
     follow_request: 'لقد قمت بإرسال طلب متابعة إلى :'
     post_follow:
+      close: أو يمكنك إغلاق هذه النافذة.
+      return: العودة إلى الملف الشخصي للمستخدم
       web: واصل إلى الويب
     title: إتباع %{acct}
   datetime:
@@ -188,6 +272,7 @@ ar:
     '404': إنّ الصفحة التي تبحث عنها لا وجود لها أصلا.
     '410': إنّ الصفحة التي تبحث عنها لم تعد موجودة.
     '500':
+      content: نحن متأسفون، لقد حدث خطأ ما مِن جانبنا.
       title: هذه الصفحة خاطئة
   exports:
     blocks: قمت بحظر
@@ -212,15 +297,23 @@ ar:
     types:
       blocking: قائمة المحظورين
       following: قائمة المستخدمين المتبوعين
+      muting: قائمة الكتم
     upload: تحميل
   invites:
     delete: تعطيل
+    expired: إنتهت صلاحيتها
     expires_in:
       '1800': 30 دقيقة
       '21600': 6 ساعات
       '3600': ساعة
       '43200': 12 ساعة
       '86400': يوم واحد
+    expires_in_prompt: أبدا
+    generate: توليد
+    max_uses_prompt: بلا حدود
+    table:
+      expires_at: تنتهي مدة صلاحيتها في
+    title: دعوة أشخاص
   landing_strip_html: "<strong>%{name}</strong> is a user on %{link_to_root_path}. You can follow them or interact with them if you have an account anywhere in the fediverse.."
   landing_strip_signup_html: إن كنت لا تملك واحدا، يمكنك <a href="%{sign_up_path}">التسجيل مِن هنا</a>.
   lists:
@@ -234,31 +327,38 @@ ar:
     acct: username@domain للحساب الجديد
     currently_redirecting: 'تم تحويل رابط ملفك الشخصي إلى :'
     proceed: حفظ
+  moderation:
+    title: الإشراف
   notification_mailer:
     digest:
-      body: 'هذه هي الأخبار المختصرة التي قد فاتتك على %{instance}وذلك منذ آخر زيارة لك في  %{since} :'
+      body: هذا هو مُلَخَّص الرسائل التي فاتتك وذلك منذ آخر زيارة لك في  %{since}
       mention: "%{name} أشار إليك في :"
       new_followers_summary:
-        one: لقد حصلت على متابع جديد !
-        other: لقد تحصلت على %{count} متتبعين جدد ! رائع !
+        one: و لقد حصلت على متابع جديد أثناء فترة غيابك ! مرحى !
+        other: و لقد تحصلت على %{count} متتبعين جدد أثناء فترة غيابك ! رائع !
       subject:
         one: "إشعار واحد منذ زيارتك الأخيرة \U0001F418"
         other: "%{count} إشعارات جديدة منذ زيارتك الأخيرة \U0001F418"
+      title: أثناء فترة غيابك …
     favourite:
       body: 'أُعجب %{name} بمنشورك :'
       subject: أُعجِب %{name} بمنشورك
     follow:
       body: "%{name} من متتبعيك الآن !"
       subject: "%{name} من متتبعيك الآن"
+      title: متابِع جديد
     follow_request:
+      action: إدارة طلبات المتابَعة
       body: طلب %{name} متابعتك
       subject: 'متابع مُعلّق : %{name}'
+      title: طلب متابَعة جديد
     mention:
       body: 'أشار إليك %{name} في :'
       subject: لقد قام %{name} بذِكرك
     reblog:
       body: 'قام %{name} بترقية منشورك :'
       subject: قام %{name} بترقية منشورك
+      title: ترقية جديدة
   number:
     human:
       decimal_units:
@@ -305,24 +405,35 @@ ar:
       blackberry: بلاك بيري
       chrome: كروم
       edge: مايكروسوفت إيدج
+      electron: إلكترون
       firefox: فايرفكس
       generic: متصفح مجهول
       ie: إنترنت إكسبلورر
+      micro_messenger: مايكرو ميسنجر
       opera: أوبرا
+      otter: أوتر
+      phantom_js: فانتوم جي آس
       qq: متصفح كيوكيو
       safari: سفاري
+      uc_browser: متصفح يوسي براوزر
+      weibo: وايبو
     current_session: الجلسة الحالية
     description: "%{browser} على %{platform}"
+    explanation: ها هي قائمة مُتصفِّحات الويب  التي تستخدِم حاليًا حساب ماستدون الخاص بك.
     ip: عنوان الإيبي
     platforms:
+      adobe_air: أدوبي إيير
       android: أندرويد
       blackberry: بلاك بيري
       chrome_os: نظام كروم أواس
       firefox_os: نظام فايرفكس أواس
+      ios: نظام آي أواس
       linux: لينكس
       mac: ماك
       other: نظام مجهول
       windows: ويندوز
+      windows_mobile: ويندوز موبايل
+      windows_phone: ويندوز فون
     title: الجلسات
   settings:
     authorized_apps: التطبيقات المرخص لها
@@ -332,6 +443,7 @@ ar:
     export: تصدير البيانات
     followers: المتابِعون المُرَخّصون
     import: إستيراد
+    migrate: تهجير الحساب
     notifications: الإخطارات
     preferences: التفضيلات
     settings: الإعدادات
@@ -349,7 +461,8 @@ ar:
       private_long: إعرضه لمتتبعيك فقط
       public: للعامة
       public_long: يمكن للجميع رؤيته
-      unlisted: Public, but do not display on the public timeline
+      unlisted: غير مُدرَج
+      unlisted_long: يُمكن لأيٍ كان رُؤيتَه و لكن لن يُعرَض على الخيوط العامة
   stream_entries:
     click_to_show: إضغط للعرض
     reblogged: رقى
diff --git a/config/locales/bg.yml b/config/locales/bg.yml
index 56a904895..cb3ed2244 100644
--- a/config/locales/bg.yml
+++ b/config/locales/bg.yml
@@ -30,7 +30,6 @@ bg:
   applications:
     invalid_url: Предоставеният URL е невалиден
   auth:
-    change_password: Идентификационни данни
     didnt_get_confirmation: Не получих инструкции за потвърждение
     forgot_password: Забравих си паролата
     login: Влизане
@@ -38,6 +37,7 @@ bg:
     register: Регистрация
     resend_confirmation: Изпрати отново инструкции за потвърждение
     reset_password: Подновяване на паролата
+    security: Идентификационни данни
     set_new_password: Задай нова парола
   authorize_follow:
     error: Възникна грешка в откриването на потребителя
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index 1092da38d..09ddb9a3e 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -2,35 +2,34 @@
 ca:
   about:
     about_hashtag_html: Aquests són toots públics etiquetats amb <strong>#%{hashtag}</strong>. Pots interactuar amb ells si tens un compte a qualsevol lloc del fediverse.
-    about_mastodon_html: Mastodon és un servidor de xarxa social <em>lliure i de codi obert</em>. Una alternativa <em>descentralitzada</em> a plataformes comercials, que evita el risc que una única companyia monopolitzi la teva comunicació. Qualsevol pot executar Mastodon i participar sense problemes en la <em>xarxa social</em>.
-    about_this: Quant a aquesta instància
-    closed_registrations: Els registres estan actualment tancats en aquesta instància.
+    about_mastodon_html: Mastodon és una xarxa social basada en protocols web oberts i en programari lliure i de codi obert. Està descentralitzat com el correu electrònic.
+    about_this: Quant a
+    closed_registrations: Actualment, el registre està tancat en aquesta instància. Malgrat això! Pots trobar una altra instància per fer-te un compte i obtenir accés a la mateixa xarxa des d'allà.
     contact: Contacte
     contact_missing: No configurat
     contact_unavailable: N/D
     description_headline: Què es %{domain}?
     domain_count_after: altres instàncies
-    domain_count_before: Connectat a
+    domain_count_before: Connectada a
     extended_description_html: |
       <h3>Un bon lloc per les regles</h3>
       <p>Encara no s'ha configurat la descripció ampliada.</p>
     features:
       humane_approach_body: Aprenent dels errors d'altres xarxes, Mastodon té com a objectiu fer eleccions ètiques de disseny per a combatre el mal ús de les xarxes socials.
       humane_approach_title: Un enfocament més humà
-      not_a_product_body: Mastodon no és una xarxa comercial. Sense publicitat, sense mineria de dades, sense jardins emmurallats. No hi ha autoritat central.
+      not_a_product_body: Mastodon no és una xarxa comercial. Sense publicitat, sense mineria de dades, sense jardins emmurallats. No hi ha cap autoritat central.
       not_a_product_title: Ets una persona, no un producte
       real_conversation_body: Amb 500 caràcters a la teva disposició i suport per a continguts granulars i avisos multimèdia, pots expressar-te de la manera que vulguis.
       real_conversation_title: Construït per a converses reals
       within_reach_body: Diverses aplicacions per a iOS, Android i altres plataformes gràcies a un ecosistema API amable amb el desenvolupador, et permet mantenir-te al dia amb els amics en qualsevol lloc..
       within_reach_title: Sempre a l'abast
-    find_another_instance: Troba altres instàncies
     generic_description: "%{domain} és un servidor a la xarxa"
     hosted_on: Mastodon allotjat a %{domain}
     learn_more: Més informació
     other_instances: Altres instàncies
     source_code: Codi font
     status_count_after: estats
-    status_count_before: que han escrit
+    status_count_before: Que han escrit
     user_count_after: usuaris registrats
     user_count_before: Tenim
     what_is_mastodon: Què és Mastodon?
@@ -41,40 +40,40 @@ ca:
     media: Mèdia
     moved_html: "%{name} s'ha mogut a %{new_profile_link}:"
     nothing_here: No hi ha res aquí!
-    people_followed_by: Usuaris a qui %{name} segueix
+    people_followed_by: Usuaris seguits per %{name}
     people_who_follow: Usuaris que segueixen %{name}
     posts: Toots
     posts_with_replies: Toots i respostes
     remote_follow: Seguiment remot
     reserved_username: El nom d'usuari està reservat
     roles:
-      admin: Admin
+      admin: Administrador
       moderator: Moderador
-    unfollow: Deixar de seguir
+    unfollow: Deixa de seguir
   admin:
     account_moderation_notes:
       account: Moderador
-      create: Crear
+      create: Crea
       created_at: Data
       created_msg: La nota de moderació s'ha creat correctament!
       delete: Suprimeix
-      destroyed_msg: S'ha destruït la nota de moderació!
+      destroyed_msg: Nota de moderació destruïda amb èxit!
     accounts:
-      are_you_sure: Estàs segur?
+      are_you_sure: N'estàs segur?
       by_domain: Domini
       confirm: Confirma
       confirmed: Confirmat
       demote: Degrada
       disable: Inhabilita
-      disable_two_factor_authentication: Desactivar 2FA
-      disabled: Inhabilita
+      disable_two_factor_authentication: Desactiva 2FA
+      disabled: Inhabilitat
       display_name: Nom de visualització
       domain: Domini
-      edit: Editar
-      email: E-mail
-      enable: Habilitar
+      edit: Edita
+      email: Correu electrònic
+      enable: Habilita
       enabled: Habilitat
-      feed_url: URL del feed
+      feed_url: URL del canal
       followers: Seguidors
       followers_url: URL dels seguidors
       follows: Segueix
@@ -87,7 +86,7 @@ ca:
         title: Localització
       login_status: Estat d'accés
       media_attachments: Adjunts multimèdia
-      memorialize: Es converteix en memoriam
+      memorialize: Converteix-lo en memorial
       moderation:
         all: Tot
         silenced: Silenciat
@@ -102,16 +101,16 @@ ca:
         most_recent: Més recent
         title: Ordre
       outbox_url: URL de la bústia de sortida
-      perform_full_suspension: Aplicar suspensió completa
+      perform_full_suspension: Aplica una suspensió completa
       profile_url: URL del perfil
       promote: Promociona
       protocol: Protocol
       public: Públic
       push_subscription_expires: La subscripció PuSH expira
-      redownload: Refrescar avatar
-      reset: Reajustar
-      reset_password: Restablir la contrasenya
-      resubscribe: Resubscribir
+      redownload: Actualitza l'avatar
+      reset: Reinicialitza
+      reset_password: Restableix la contrasenya
+      resubscribe: Torna a subscriure
       role: Permisos
       roles:
         admin: Administrador
@@ -131,38 +130,38 @@ ca:
       title: Comptes
       undo_silenced: Deixa de silenciar
       undo_suspension: Desfés la suspensió
-      unsubscribe: Donar-se de baixa
+      unsubscribe: Cancel·la la subscripció
       username: Nom d'usuari
       web: Web
     action_logs:
       actions:
         confirm_user: "%{name} ha confirmat l'adreça de correu electrònic de l'usuari %{target}"
-        create_custom_emoji: "%{name} ha penjat un nou emoji %{target}"
-        create_domain_block: "%{name} ha bloquejat el domini %{target}"
+        create_custom_emoji: "%{name} ha pujat un nou emoji %{target}"
+        create_domain_block: "%{name} ha blocat el domini %{target}"
         create_email_domain_block: "%{name} ha afegit a la llista negra el domini del correu electrònic %{target}"
         demote_user: "%{name} ha degradat l'usuari %{target}"
-        destroy_domain_block: "%{name} ha desbloquejat el domini %{target}"
+        destroy_domain_block: "%{name} ha desblocat el domini %{target}"
         destroy_email_domain_block: "%{name} ha afegit a la llista negra el domini de correu electrònic %{target}"
-        destroy_status: "%{name} estat eliminat per %{target}"
+        destroy_status: "%{name} eliminat l'estat per %{target}"
         disable_2fa_user: "%{name} ha desactivat el requisit de dos factors per a l'usuari %{target}"
         disable_custom_emoji: "%{name} ha desactivat l'emoji %{target}"
         disable_user: "%{name} ha desactivat l'accés per a l'usuari %{target}"
         enable_custom_emoji: "%{name} ha activat l'emoji %{target}"
         enable_user: "%{name} ha activat l'accés per a l'usuari %{target}"
-        memorialize_account: "%{name} ha convertit el compte %{target} en una pàgina de memòria"
+        memorialize_account: "%{name} ha convertit el compte %{target} en una pàgina de memorial"
         promote_user: "%{name} ha promogut l'usuari %{target}"
-        reset_password_user: "%{name} restablirà la contrasenya de l'usuari %{target}"
+        reset_password_user: "%{name} ha restablert la contrasenya de l'usuari %{target}"
         resolve_report: "%{name} ha descartat l'informe %{target}"
         silence_account: "%{name} ha silenciat el compte de %{target}"
         suspend_account: "%{name} ha suspès el compte de %{target}"
         unsilence_account: "%{name} ha silenciat el compte de %{target}"
         unsuspend_account: "%{name} ha llevat la suspensió del compte de %{target}"
-        update_custom_emoji: "%{name} ha actualizat l'emoji %{target}"
-        update_status: "%{name} estat actualizat per %{target}"
+        update_custom_emoji: "%{name} ha actualitzat l'emoji %{target}"
+        update_status: "%{name} estat actualitzat per %{target}"
       title: Registre d'auditoria
     custom_emojis:
       by_domain: Domini
-      copied_msg: S'ha creat correctament la còpia local del emoji
+      copied_msg: S'ha creat correctament la còpia local de l'emoji
       copy: Copia
       copy_failed_msg: No s'ha pogut fer una còpia local d'aquest emoji
       created_msg: Emoji creat amb èxit!
@@ -176,11 +175,11 @@ ca:
       image_hint: PNG de fins a 50 KB
       listed: Enumerat
       new:
-        title: Afegeix nou emoji personalitzat
+        title: Afegeix emoji personalitzat nou
       overwrite: Sobreescriure
       shortcode: Codi curt
       shortcode_hint: Com a mínim 2 caràcters, només caràcters alfanumèrics i guions baixos
-      title: Emojis personatlitzats
+      title: Emojis personalitzats
       unlisted: Sense classificar
       update_failed_msg: No s'ha pogut actualitzar aquest emoji
       updated_msg: Emoji s'ha actualitzat correctament!
@@ -192,15 +191,15 @@ ca:
       domain: Domini
       new:
         create: Crea un bloqueig
-        hint: El bloqueig de domini no impedirà la creació de nous comptes en la base de dades, però s´aplicaran de manera retroactiva mètodes de moderació específics sobre aquests comptes.
+        hint: El bloqueig de domini no impedirà la creació de nous comptes en la base de dades, però s'aplicaran de manera retroactiva mètodes de moderació específics sobre aquests comptes.
         severity:
           desc_html: "<strong>Silenci</strong> farà les publicacions del compte invisibles a tothom que no l'estigui seguint. <strong>La suspensió</strong> eliminarà tots els continguts, multimèdia i les dades del perfil del compte. Usa <strong>Cap</strong> si només vols rebutjar el fitxers multimèdia."
           noop: Cap
           silence: Silenci
           suspend: Suspensió
-        title: Nou bloqueig de domini
+        title: Bloqueig de domini nou
       reject_media: Rebutja els fitxers multimèdia
-      reject_media_hint: Elimina els fitxers multimèdia emmagatzamats localment i impideix descarregar-ne cap en el futur. Irrellevant en les suspensions
+      reject_media_hint: Elimina els fitxers multimèdia emmagatzemats localment i impedeix baixar-ne cap en el futur. Irrellevant en les suspensions
       severities:
         noop: Cap
         silence: Silenci
@@ -216,33 +215,33 @@ ca:
         title: Desfés el bloqueig de domini de %{domain}
         undo: Desfés
       title: Bloquejos de domini
-      undo: Desfer
+      undo: Desfés
     email_domain_blocks:
-      add_new: Afegir nou
+      add_new: Afegeix
       created_msg: S'ha creat el bloc de domini de correu electrònic
       delete: Suprimeix
       destroyed_msg: S'ha eliminat correctament el bloc del domini de correu
       domain: Domini
       new:
-        create: Crear bloc
-        title: Nou bloc de domini de correu electrònic
-      title: Bloc de domini de correu electrònic
+        create: Afegeix un domini
+        title: Nova adreça de correu en la llista negra
+      title: Llista negra de correus electrònics
     instances:
       account_count: Comptes coneguts
       domain_name: Domini
-      reset: Restablir
+      reset: Restableix
       search: Cerca
       title: Instàncies conegudes
     invites:
       filter:
-        all: Tot
+        all: Totes
         available: Disponible
         expired: Caducat
         title: Filtre
       title: Convida
     reports:
       action_taken_by: Mesures adoptades per
-      are_you_sure: Estàs segur?
+      are_you_sure: N'estàs segur?
       comment:
         label: Comentari
         none: Cap
@@ -253,7 +252,7 @@ ca:
         'false': Mostra els fitxers multimèdia adjunts
         'true': Amaga els fitxers multimèdia adjunts
       report: 'Informe #%{id}'
-      report_contents: Continguts
+      report_contents: Contingut
       reported_account: Compte reportat
       reported_by: Reportat per
       resolved: Resolt
@@ -266,23 +265,26 @@ ca:
       view: Visualització
     settings:
       activity_api_enabled:
-        desc_html: Compte d'estatus publicats localment, usuaris actius i registres nous en cubs setmanals
+        desc_html: Compte d'estatus publicats localment, usuaris actius i registres nous en períodes setmanals
         title: Publica estadístiques agregades sobre l'activitat de l'usuari
       bootstrap_timeline_accounts:
-        desc_html: Separa diversos noms d'usuari amb comes. Només funcionaran els comptes locals i desbloquejats. El valor predeterminat quan està buit és tots els administradors locals..
-        title: El seguiment per defecte per als nous usuaris
+        desc_html: Separa diversos noms d'usuari amb comes. Només funcionaran els comptes locals i desblocats. El valor predeterminat quan està buit és tots els administradors locals.
+        title: El seguiment per defecte per als usuaris nous
       contact_information:
-        email: Introdueix una adreça de correu electrònic pùblica
-        username: Introdueix un nom d'usuari
+        email: Introdueix una adreça de correu electrònic píblica
+        username: Nom d'usuari del contacte
+      hero:
+        desc_html: Es mostra en pàgina frontal. Recomanat 600x100px al menys. Si no es configura es mostrarà el de la instància
+        title: Imatge d’heroi
       peers_api_enabled:
         desc_html: Els noms de domini que ha trobat aquesta instància al fediverse
         title: Publica la llista d'instàncies descobertes
       registrations:
         closed_message:
-          desc_html: Apareix en la primera pàgina quan es tanquen els registres<br>Pots utilitzar etiquetes HTML
+          desc_html: Apareix en la primera pàgina quan es tanquen els registres. Pots utilitzar etiquetes HTML
           title: Missatge de registre tancat
         deletion:
-          desc_html: Permet a qualsevol esborrar el seu compte
+          desc_html: Permet a qualsevol usuari d'esborrar el seu compte
           title: Obre la supressió del compte
         min_invite_role:
           disabled: Ningú
@@ -290,19 +292,22 @@ ca:
         open:
           desc_html: Permet que qualsevol pugui crear un compte
           title: Registre obert
+      show_known_fediverse_at_about_page:
+        desc_html: Quan s'activa, mostrarà tots els toots de tot el fedivers conegut en vista prèvia. En cas contrari, només es mostraran toots locals.
+        title: Mostra el fedivers conegut en vista prèvia de la línia de temps
       show_staff_badge:
-        desc_html: Mostra una insígnia de personal en una pàgina d'usuari
+        desc_html: Mostra una insígnia de personal en la pàgina d'usuari
         title: Mostra insígnia de personal
       site_description:
         desc_html: Paràgraf introductori a la pàgina principal i en etiquetes meta. Pots utilitzar etiquetes HTML, en particular <code>&lt;a&gt;</code> i <code>&lt;em&gt;</code>.
-        title: Descripció del lloc
+        title: Descripció de la instància
       site_description_extended:
         desc_html: Un bon lloc per al codi de conducta, regles, directrius i altres coses que distingeixen la vostra instància. Pots utilitzar etiquetes HTML
         title: Descripció ampliada del lloc
       site_terms:
         desc_html: Pots escriure la teva pròpia política de privadesa, els termes del servei o d'altres normes legals. Pots utilitzar etiquetes HTML
         title: Termes del servei personalitzats
-      site_title: Títol del lloc
+      site_title: Nom de la instància
       thumbnail:
         desc_html: S'utilitza per obtenir visualitzacions prèvies a través d'OpenGraph i API. Es recomana 1200x630px
         title: Miniatura de la Instància
@@ -331,44 +336,55 @@ ca:
       expires_in: Expira en
       last_delivery: Últim lliurament
       title: WebSub
-      topic: Tòpic
+      topic: Tema
     title: Administració
   admin_mailer:
     new_report:
       body: "%{reporter} ha informat de %{target}"
-      subject: Nou informe per a %{instance} (#%{id})
+      subject: Informe nou per a %{instance} (#%{id})
   application_mailer:
+    notification_preferences: Canvia les preferències de correu
     salutation: "%{name},"
     settings: 'Canvia les preferències de correu: %{link}'
-    view: 'Vista:'
+    view: 'Visualització:'
+    view_profile: Mostra el perfil
+    view_status: Mostra l'estat
   applications:
     created: L'aplicació s'ha creat correctament
     destroyed: L'aplicació s'ha suprimit correctament
-    invalid_url: La URL proporcionada es incorrecte
-    regenerate_token: Regenerar token d'accés
-    token_regenerated: Token d'accés s'ha generat correctament
-    warning: Aneu amb compte amb aquestes dades. No ho compartiu mai amb ningú!
-    your_token: El token d'accés
+    invalid_url: L'URL proporcionat no és correcte
+    regenerate_token: Torna a generar l'identificador d'accés
+    token_regenerated: L'identificador d'accés s'ha generat correctament
+    warning: Aneu amb compte amb aquestes dades. No les compartiu mai amb ningú!
+    your_token: El teu identificador d'accés
   auth:
     agreement_html: En inscriure't, acceptes seguir <a href="%{rules_path}">els nostres termes del servei</a> i <a href="%{terms_path}">la nostra política de privadesa</a>.
-    change_password: Canvia la contrasenya
-    delete_account: Esborra el compte
-    delete_account_html: Si vols esborrar el teu compte pots <a href="%{path}">fer-ho aquí</a>. Se't demanarà confirmació.
-    didnt_get_confirmation: No ha rebut el correu de confirmació?
+    change_password: Contrasenya
+    confirm_email: Confirmar correu electrònic
+    delete_account: Suprimeix el compte
+    delete_account_html: Si vols suprimir el compte pots <a href="%{path}">fer-ho aquí</a>. Se't demanarà confirmació.
+    didnt_get_confirmation: No has rebut el correu de confirmació?
     forgot_password: Has oblidat la contrasenya?
-    invalid_reset_password_token: L'enllaç de restabliment de la contrasenya no és vàlid o ha caducat. Torna-ho a provar..
+    invalid_reset_password_token: L'enllaç de restabliment de la contrasenya no és vàlid o ha caducat. Torna-ho a provar.
     login: Inicia sessió
     logout: Tanca sessió
     migrate_account: Mou a un compte diferent
     migrate_account_html: Si vols redirigir aquest compte a un altre diferent, el pots  <a href="%{path}">configurar aquí</a>.
-    register: Registra't
+    or: o
+    or_log_in_with: O inicia sessió amb
+    providers:
+      cas: CAS
+      saml: SAML
+    register: Registre
+    register_elsewhere: Registra't en un altre servidor
     resend_confirmation: Torna a enviar el correu de confirmació
     reset_password: Restableix la contrasenya
-    set_new_password: Estableix nova contrasenya
+    security: Seguretat
+    set_new_password: Estableix una contrasenya nova
   authorize_follow:
-    error: Malauradament, ha ocorregut un error buscant el compte remot
+    error: Malauradament, ha ocorregut un error cercant el compte remot
     follow: Segueix
-    follow_request: 'Heu enviat una sol·licitud de seguiment a:'
+    follow_request: 'Has enviat una sol·licitud de seguiment a:'
     following: 'Perfecte! Ara segueixes:'
     post_follow:
       close: O bé, pots tancar aquesta finestra.
@@ -391,26 +407,33 @@ ca:
       x_seconds: "%{count} s"
   deletes:
     bad_password_msg: Bon intent hackers! La contrasenya no és correcta
-    confirm_password: Introdueix la contrasenya actual per a verificar la teva identitat
+    confirm_password: Introdueix la contrasenya actual per a verificar la identitat
     description_html: Això eliminarà de forma <strong>irreversible i permanent</strong> el contingut del teu compte i el desactivarà. El teu nom d'usuari romandrà reservat per evitar que algú volgués fer-se passar per tu.
-    proceed: Suprimir el compte
-    success_msg: El teu compte s'ha eliminat correctament
-    warning_html: Només està garantida l'eliminació d'aquesta particular instància. El contingut que ha estat àmpliament compartit que deixi petjades. Els servidors fora de línia i els que ja no estan subscrits no actualitzaran les seves bases de dades.
+    proceed: Suprimeix el compte
+    success_msg: El compte s'ha eliminat correctament
+    warning_html: Només és garantida l'eliminació d'aquesta particular instància. El contingut que s'ha compartit àmpliament deixa petjades. Els servidors fora de línia i els que ja no estan subscrits no actualitzaran les seves bases de dades.
     warning_title: Disponibilitat de contingut disseminat
   errors:
     '403': No tens permís per a veure aquesta pàgina.
     '404': La pàgina que estàs cercant no existeix.
     '410': La pàgina que estàs cercant ja no existeix.
     '422':
-      content: La verificació de seguretat ha fallat. Bloques les galetes?
+      content: La verificació de seguretat ha fallat. Tens les galetes blocades?
       title: La verificació de seguretat ha fallat
     '429': Estrangulat
     '500':
       content: Ho sentim, però alguna cosa ha fallat a la nostra banda.
-      title: Aquesta pàgina no es correcte
-    noscript_html: Per utilitzar Mastodon si us plau activa JavaScript. També podeu provar una de les <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md"> aplicacions natives</a> per Mastodon per a la vostra plataforma.
+      title: Aquesta pàgina no es correcta
+    noscript_html: Per a utilitzar Mastodon, activa el JavaScript. També pots provar una de les <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md"> aplicacions natives</a> de Mastodon per a la vostra plataforma.
   exports:
-    blocks: Persones que has bloquejat
+    archive_takeout:
+      date: Data
+      download: Descarrega l’arxiu
+      hint_html: Pots sol·licitar un arxiu dels teus <strong>toots i els fitxers multimèdia pujats</strong>. Les dades exportades tindran el format ActivityPub, llegible per qualsevol programari compatible.
+      in_progress: Compilant el teu arxiu...
+      request: Sol·licita el teu arxiu
+      size: Tamany
+    blocks: Persones que has blocat
     csv: CSV
     follows: Persones que segueixes
     mutes: Persones silenciades
@@ -481,29 +504,38 @@ ca:
     title: Moderació
   notification_mailer:
     digest:
-      body: 'Un resum del que et vas perdre en %{instance} desde la darrera visita el %{since}:'
+      action: Veure totes les notificacions
+      body: Un resum del que et vas perdre en %{instance} desde la darrera visita el %{since}
       mention: "%{name} t'ha mencionat en:"
       new_followers_summary:
-        one: Visca!. Algú més t´ha començat a seguir!
-        other: Genial!. Et segueixen %{count} persones més!
+        one: A més, has adquirit un nou seguidor durant la teva absència! Visca!
+        other: A més, has adquirit %{count} nous seguidors mentre estaves fora! Increïble!
       subject:
         one: "1 notificació nova des de la darrera visita \U0001F418"
         other: "%{count} notificacions noves des de la darrera visita \U0001F418"
+      title: Durant la teva absència…
     favourite:
       body: "%{name} ha marcat com a favorit el teu estat:"
       subject: "%{name} ha marcat com a favorit el teu estat"
+      title: Favorit nou
     follow:
       body: "%{name} t'està seguint!"
       subject: "%{name} t'està seguint"
+      title: Seguidor nou
     follow_request:
+      action: Gestiona les sol·licituds de seguiment
       body: "%{name} ha sol·licitat seguir-te"
       subject: 'Seguidor pendent: %{name}'
+      title: Nova sol·licitud de seguiment
     mention:
+      action: Respon
       body: "%{name} t'ha mencionat en:"
       subject: "%{name} t'ha mencionat"
+      title: Menció nova
     reblog:
       body: "%{name} ha impulsat el teu estat:"
       subject: "%{name} ha retootejat el teu estat"
+      title: Nou impuls
   number:
     human:
       decimal_units:
@@ -514,13 +546,15 @@ ca:
           quadrillion: Q
           thousand: m
           trillion: T
-          unit: ''
+          unit: " "
   pagination:
-    next: Següent
+    newer: Més recent
+    next: Endavant
+    older: Més vell
     prev: Enrere
     truncate: "&hellip;"
   preferences:
-    languages: Idiomes
+    languages: Llengues
     other: Altre
     publishing: Publicació
     web: Web
@@ -539,7 +573,7 @@ ca:
     reblog:
       title: "%{name} t'ha retootejat"
   remote_follow:
-    acct: Escriu l'usuari@domini de la persona que vols seguir
+    acct: Escriu el teu usuari@domini des del qual vols seguir
     missing_resource: No s'ha pogut trobar la URL de redirecció necessaria per al compte
     proceed: Comença a seguir
     prompt: 'Seguiràs a:'
@@ -619,7 +653,7 @@ ca:
   stream_entries:
     click_to_show: Clic per mostrar
     pinned: Toot fixat
-    reblogged: impulsat
+    reblogged: ha impulsat
     sensitive_content: Contingut sensible
   terms:
     body_html: |
@@ -629,7 +663,7 @@ ca:
 
       <p>Recopilem informació teva quan et registres en aquesta instància i recopilem dades quan participes en el fòrum llegint, escrivint i avaluant el contingut aquí compartit.</p>
 
-      <p>En registrar-te en aquesta instància, se't pot demanar que introduexisu el teu nom i l'adreça de correu electrònic. També pots visitar el nostre lloc sense registrar-te. La teva adreça de correu electrònic es verificarà mitjançant un correu electrònic que conté un enllaç únic. Si es visita aquest enllaç, sabem que controles l'adreça de correu electrònic.</p>
+      <p>En registrar-te en aquesta instància, se't pot demanar que introduexis el teu nom i l'adreça de correu electrònic. També pots visitar el nostre lloc sense registrar-te. La teva adreça de correu electrònic es verificarà mitjançant un correu electrònic que conté un enllaç únic. Si es visita aquest enllaç, sabem que controles l'adreça de correu electrònic.</p>
 
       <p>Quan es registra i publica, registrem l'adreça IP de la qual es va originar la publicació. També podrem conservar els registres del servidor que inclouen l'adreça IP de cada sol·licitud al nostre servidor.</p>
 
@@ -710,7 +744,31 @@ ca:
     recovery_instructions_html: Si mai perds l'accéss al teu telèfon pots utilitzar un dels codis de recuperació a continuació per a recuperar l'accés al teu compte. <strong>Cal mantenir els codis de recuperació en lloc segur</strong>. Per exemple, imprimint-los i guardar-los amb altres documents importants.
     setup: Establir
     wrong_code: El codi introduït no és vàlid! És correcta l'hora del servidor i del dispositiu?
+  user_mailer:
+    backup_ready:
+      explanation: Has sol·licitat una copia completa del teu compte Mastodon. Ara ja està a punt per descàrrega!
+      subject: El teu arxiu està preparat per a descàrrega
+      title: Recollida del arxiu
+    welcome:
+      edit_profile_action: Configurar perfil
+      edit_profile_step: Pots personalitzar el teu perfil penjant un avatar, un encapçalament, canviant el teu nom de visualització i molt més. Si prefereixes revisar els seguidors nous abans de que et puguin seguir, pots blocar el teu compte.
+      explanation: Aquests són alguns consells per començar
+      final_action: Comença a publicar
+      final_step: 'Comença a publicar! Fins i tot sense seguidors, els altres poden veure els teus missatges públics, per exemple, a la línia de temps local i a les etiquetes ("hashtags"). És possible que vulguis presentar-te amb l''etiqueta #introductions.'
+      full_handle: El teu nom d'usuari sencer
+      full_handle_hint: Això és el que has de dir als teus amics perquè puguin enviar-te missatges o seguir-te des d'una altra instància.
+      review_preferences_action: Canviar preferències
+      review_preferences_step: Assegura't d'establir les teves preferències, com ara els correus electrònics que vols rebre o el nivell de privadesa per defecte que t'agradaria que tinguin les teves entrades. Si no tens malaltia de moviment, pots optar per habilitar la reproducció automàtica de GIF.
+      subject: Benvingut/da a Mastodon
+      tip_bridge_html: Si vens de Twitter, pots trobar els teus amics a Mastodon mitjançant l'aplicació <a href="%{bridge_url}"> bridge </a>. Només funciona si també ells usen aquesta aplicació pont!
+      tip_federated_timeline: La línia de temps federada és el cabal principal de la xarxa Mastodon. Però només inclou les persones a les quals els teus veïns estan subscrits, de manera que no està complet.
+      tip_following: Per defecte segueixes als administradors del servidor. Per trobar més persones interessants, consulta les línies de temps local i federada.
+      tip_local_timeline: La línia de temps local és la vista del flux de publicacions dels usuaris de %{instance}. Aquests usuaris són els teus veïns més propers!
+      tip_mobile_webapp: Si el teu navegador del mòbil t'ofereix afegir Mastodon a la teva pantalla d'inici, podràs rebre notificacions "push". Es comporta com una aplicació nativa en molts aspectes!
+      tips: Consells
+      title: Benvingut a bord, %{name}!
   users:
     invalid_email: L'adreça de correu no és correcta
     invalid_otp_token: El codi de dos factors no és correcte
+    seamless_external_login: Has iniciat sessió via un servei extern per tant els ajustos de contrasenya i correu electrònic no estan disponibles.
     signed_in_as: 'Sessió iniciada com a:'
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 7d0cf6349..6233d299e 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -23,7 +23,6 @@ de:
       real_conversation_title: Für das echte Gespräch gemacht
       within_reach_body: Verschiedene Apps für iOS, Android und andere Plattformen erlauben dir dank unserem blühenden API-Ökosystem, dich von überall auf dem Laufenden zu halten.
       within_reach_title: Immer für dich da
-    find_another_instance: Eine andere Instanz finden
     generic_description: "%{domain} ist ein Server im Netzwerk"
     hosted_on: Mastodon, beherbergt auf %{domain}
     learn_more: Mehr erfahren
@@ -61,7 +60,7 @@ de:
       destroyed_msg: Moderationsnotiz erfolgreich gelöscht!
     accounts:
       are_you_sure: Bist du sicher?
-      by_domain: Domain
+      by_domain: Domäne
       confirm: Bestätigen
       confirmed: Bestätigt
       demote: Degradieren
@@ -116,10 +115,11 @@ de:
       roles:
         admin: Administrator
         moderator: Moderator
+        staff: Mitarbeiter
         user: Nutzer
       salmon_url: Salmon-URL
       search: Suche
-      shared_inbox_url: Shared Inbox URL
+      shared_inbox_url: Geteilter Posteingang URL
       show:
         created_reports: Meldungen durch dieses Konto
         report: Meldung
@@ -143,10 +143,13 @@ de:
         destroy_domain_block: "%{name} hat die Domain %{target} entblockt"
         destroy_email_domain_block: "%{name} hat die E-Mail-Domain %{target} gewhitelistet"
         destroy_status: "%{name} hat Status von %{target} entfernt"
-        disable_custom_emoji: "%{name} hat Emoji %{target} deaktiviert"
+        disable_2fa_user: "%{name} hat Zwei-Faktor-Anforderung für Benutzer %{target} deaktiviert"
+        disable_custom_emoji: "%{name} hat das %{target} Emoji deaktiviert"
+        disable_user: "%{name} hat den Login für Benutzer  %{target} deaktiviert"
         enable_custom_emoji: "%{name} hat das %{target} Emoji aktiviert"
         enable_user: "%{name} hat die Anmeldung für den Benutzer %{target} aktiviert"
         memorialize_account: "%{name} hat %{target}s Profil in eine Gedenkseite umgewandelt"
+        promote_user: "%{name} hat %{target} befördert"
         reset_password_user: "%{name} hat das Passwort für den Benutzer %{target} zurückgesetzt"
         resolve_report: "%{name} hat die Meldung %{target} abgelehnt"
         silence_account: "%{name} hat %{target}s Account stummgeschaltet"
@@ -157,6 +160,7 @@ de:
         update_status: "%{name} hat den Status von %{target} aktualisiert"
       title: Überprüfungsprotokoll
     custom_emojis:
+      by_domain: Domain
       copied_msg: Eine lokale Kopie des Emojis wurde erstellt
       copy: Kopieren
       copy_failed_msg: Es konnte keine lokale Kopie des Emojis erstellt werden
@@ -169,11 +173,15 @@ de:
       enable: Aktivieren
       enabled_msg: Das Emoji wurde aktiviert
       image_hint: PNG bis 50 kB
+      listed: Gelistet
       new:
         title: Eigenes Emoji hinzufügen
+      overwrite: Überschreiben
       shortcode: Shortcode
       shortcode_hint: Mindestens 2 Zeichen, nur Buchstaben, Ziffern und Unterstriche
       title: Eigene Emojis
+      unlisted: Ungelistet
+      update_failed_msg: Konnte dieses Emoji nicht aktualisieren
       updated_msg: Emoji erfolgreich aktualisiert!
       upload: Hochladen
     domain_blocks:
@@ -256,12 +264,21 @@ de:
       unresolved: Ungelöst
       view: Ansehen
     settings:
+      activity_api_enabled:
+        desc_html: Anzahl der lokal geposteten Beiträge, aktiven Nutzern und neuen Registrierungen in wöchentlichen Zusammenfassungen
+        title: Veröffentliche gesamte Statistiken über Benutzeraktivitäten
       bootstrap_timeline_accounts:
         desc_html: Mehrere Profilnamen durch Kommata trennen. Funktioniert nur mit lokalen und nicht gesperrten Konten. Standardwert bei freigelassenem Feld sind alle lokalen Admins.
         title: Konten, denen Neu-Angemeldete automatisch folgen
       contact_information:
         email: Öffentliche E-Mail-Adresse
         username: Profilname für die Kontaktaufnahme
+      hero:
+        desc_html: Wird auf der Startseite angezeigt. Mindestens 600x100px sind empfohlen. Wenn es nicht gesetzt wurde, wird das Instanz-Thumbnail dafür verwendet
+        title: Bild für Startseite
+      peers_api_enabled:
+        desc_html: Domain-Namen dieser Instanz, die im Fediverse gefunden wurden
+        title: Veröffentliche Liste von gefundenen Instanzen
       registrations:
         closed_message:
           desc_html: Wird auf der Frontseite angezeigt, wenn die Registrierung geschlossen ist. Du kannst HTML-Tags benutzen
@@ -271,9 +288,16 @@ de:
           title: Kontolöschung erlauben
         min_invite_role:
           disabled: Niemand
+          title: Einladungen erlauben von
         open:
           desc_html: Allen erlauben, ein Konto zu erstellen
           title: Registrierung öffnen
+      show_known_fediverse_at_about_page:
+        desc_html: Wenn aktiviert, wird es alle Beiträge aus dem bereits bekannten Teil des Fediversums auf der Startseite anzeigen. Andernfalls werden lokale Beitrage der Instanz angezeigt.
+        title: Verwende öffentliche Zeitleiste für die Vorschau
+      show_staff_badge:
+        desc_html: Zeige Mitarbeiter-Badge auf Benutzerseite
+        title: Zeige Mitarbeiter-Badge
       site_description:
         desc_html: Wird als Absatz auf der Frontseite angezeigt und als Meta-Tag benutzt. Du kannst HTML-Tags benutzen, insbesondere <code>&lt;a&gt;</code> und <code>&lt;em&gt;</code>.
         title: Beschreibung der Instanz
@@ -319,20 +343,24 @@ de:
       body: "%{reporter} hat %{target} gemeldet"
       subject: Neue Meldung auf %{instance} (#%{id})
   application_mailer:
+    notification_preferences: Ändere E-Mail-Einstellungen
     salutation: "%{name},"
     settings: 'E-Mail-Einstellungen ändern: %{link}'
     view: 'Ansehen:'
+    view_profile: Zeige Profil
+    view_status: Zeige Status
   applications:
-    created: Anwendung erstellt
-    destroyed: Anwendung gelöscht
+    created: Anwendung erfolgreich erstellt
+    destroyed: Anwendung erfolgreich gelöscht
     invalid_url: Die angegebene URL ist ungültig
     regenerate_token: Zugangs-Token neu erstellen
     token_regenerated: Zugangs-Token neu erstellt
-    warning: Sei mit diesen Daten sehr vorsichtig! Teile sie niemandem mit.
+    warning: Sei mit diesen Daten sehr vorsichtig. Teile sie mit niemandem!
     your_token: Dein Zugangs-Token
   auth:
-    agreement_html: Indem du dich registrierst, erklärst du dich mit unseren <a href="%{rules_path}">Geschäftsbedingungen</a> und der <a href="%{terms_path}">Datenschutzerklärung</a> einverstanden.
-    change_password: Sicherheit
+    agreement_html: Indem du dich registrierst, erklärst du dich mit den Regeln, die <a href="%{rules_path}">auf dieser Instanz gelten</a> und der <a href="%{terms_path}">Datenschutzerklärung</a> einverstanden.
+    change_password: Passwort
+    confirm_email: E-Mail bestätigen
     delete_account: Konto löschen
     delete_account_html: Falls du dein Konto löschen willst, kannst du <a href="%{path}">hier damit fortfahren</a>. Du wirst um Bestätigung gebeten werden.
     didnt_get_confirmation: Keine Bestätigungs-Mail erhalten?
@@ -340,9 +368,18 @@ de:
     invalid_reset_password_token: Das Token zum Zurücksetzen des Passworts ist ungültig oder abgelaufen. Bitte fordere ein neues an.
     login: Anmelden
     logout: Abmelden
+    migrate_account: Ziehe zu einem anderen Account um
+    migrate_account_html: Wenn du es wünschst diesen Account zu einem anderen umzuziehen, dann kannst du <a href="%{path}">es hier einstellen</a>.
+    or: oder
+    or_log_in_with: Oder anmelden mit
+    providers:
+      cas: CAS
+      saml: SAML
     register: Registrieren
+    register_elsewhere: Registrieren auf einem anderen Server
     resend_confirmation: Bestätigungs-Mail erneut versenden
     reset_password: Passwort zurücksetzen
+    security: Sicherheit
     set_new_password: Neues Passwort setzen
   authorize_follow:
     error: Das Profil konnte nicht geladen werden
@@ -389,6 +426,13 @@ de:
       title: Diese Seite ist kaputt
     noscript_html: Bitte aktiviere JavaScript, um die Mastodon-Web-Anwendung zu verwenden. Alternativ kannst du auch eine der <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">nativen Mastodon-Anwendungen</a> für deine Plattform probieren.
   exports:
+    archive_takeout:
+      date: Datum
+      download: Dein Archiv herunterladen
+      hint_html: Du kannst ein Archiv deiner <strong>Beiträge und hochgeladenen Medien</strong> anfragen. Die exportieren Daten werden im ActivityPub-Format gespeichert, dass lesbar mit jeder Software ist, die das Format unterstützt.
+      in_progress: Stelle dein Archiv zusammen...
+      request: Dein Archiv anfragen
+      size: Größe
     blocks: Du hast blockiert
     csv: CSV
     follows: Du folgst
@@ -421,8 +465,10 @@ de:
       following: Folgeliste
       muting: Stummschaltungsliste
     upload: Hochladen
+  in_memoriam_html: In Gedenken.
   invites:
     delete: Deaktivieren
+    expired: Abgelaufen
     expires_in:
       '1800': 30 Minuten
       '21600': 6 Stunden
@@ -435,42 +481,61 @@ de:
       one: 1 mal verwendet
       other: "%{count} mal verwendet"
     max_uses_prompt: Kein Limit
+    prompt: Generiere und teile Links um Zugang zu dieser Instanz zu geben
+    table:
+      expires_at: Läuft ab
+      uses: Verwendungen
+    title: Leute Einladen
   landing_strip_html: "<strong>%{name}</strong> hat ein Profil auf %{link_to_root_path}. Du kannst folgen oder interagieren, sofern du ein Konto irgendwo im Fediversum hast."
   landing_strip_signup_html: Wenn nicht, kannst du dich <a href="%{sign_up_path}">hier anmelden</a>.
+  lists:
+    errors:
+      limit: Du hast die maximale Anzahl an Listen erreicht
   media_attachments:
     validations:
       images_and_video: Es kann kein Video an einen Beitrag, der bereits Bilder enthält, angehängt werden
       too_many: Es können nicht mehr als 4 Bilder angehängt werden
   migrations:
     acct: benutzername@domain des neuen Accounts
+    currently_redirecting: 'Deine Profilweiterleitung wurde gesetzt auf:'
     proceed: Speichern
+    updated_msg: Deine Account-Migrationseinstellungen wurden erfolgreich aktualisiert!
   moderation:
     title: Moderation
   notification_mailer:
     digest:
-      body: 'Hier ist eine kurze Zusammenfasung dessen, was du auf %{instance} seit deinem letzten Besuch am %{since} verpasst hast:'
+      action: Zeige alle Benachrichtigungen
+      body: Hier ist eine kurze Zusammenfassung der Nachrichten, die du seit deinem letzten Besuch am %{since} verpasst hast
       mention: "%{name} hat dich erwähnt:"
       new_followers_summary:
-        one: Ein weiteres Wesen folgt dir nun! Juhu!
-        other: "%{count} weitere Wesen folgen dir nun! Großartig!"
+        one: Außerdem ist dir seit du weg warst ein weiteres Wesen gefolgt! Juhu!
+        other: Außerdem sind dir seit du weg warst %{count} weitere Wesen gefolgt! Großartig!
       subject:
         one: "1 neue Mitteilung seit deinem letzten Besuch \U0001F418"
         other: "%{count} neue Mitteilungen seit deinem letzten Besuch \U0001F418"
+      title: In deiner Abwesenheit…
     favourite:
       body: 'Dein Beitrag wurde von %{name} favorisiert:'
       subject: "%{name} hat deinen Beitrag favorisiert"
+      title: Neue Favorisierung
     follow:
       body: "%{name} folgt dir jetzt!"
       subject: "%{name} folgt dir jetzt"
+      title: Neuer Follower
     follow_request:
-      body: "%{name} möchte dir folgen:"
-      subject: "%{name} möchte dir folgen"
+      action: Verwalte Folge-Anfragen
+      body: "%{name} möchte dir folgen"
+      subject: 'Ausstehender Follower: %{name}'
+      title: Neue Folge-Anfrage
     mention:
+      action: Antworten
       body: "%{name} hat dich erwähnt:"
       subject: "%{name} hat dich erwähnt"
+      title: Neue Erwähnung
     reblog:
       body: "%{name} hat deinen Beitrag geteilt:"
       subject: "%{name} hat deinen Beitrag geteilt"
+      title: Dein Beitrag wurde geteilt
   number:
     human:
       decimal_units:
@@ -483,7 +548,9 @@ de:
           trillion: T
           unit: ''
   pagination:
+    newer: Neuer
     next: Vorwärts
+    older: Älter
     prev: Zurück
     truncate: "&hellip;"
   preferences:
@@ -518,12 +585,14 @@ de:
       blackberry: Blackberry
       chrome: Chrome
       edge: Microsoft Edge
+      electron: Electron
       firefox: Firefox
       generic: Unbekannter Browser
       ie: Internet Explorer
       micro_messenger: MicroMessenger
       nokia: Nokia S40 Ovi Browser
       opera: Opera
+      otter: Otter
       phantom_js: PhantomJS
       qq: QQ Browser
       safari: Safari
@@ -542,7 +611,7 @@ de:
       ios: iOS
       linux: Linux
       mac: Mac
-      other: unbekannter Plattform
+      other: unbekannte Plattform
       windows: Windows
       windows_mobile: Windows Mobile
       windows_phone: Windows Phone
@@ -558,20 +627,22 @@ de:
     export: Datenexport
     followers: Autorisierte Folgende
     import: Datenimport
+    migrate: Account-Umzug
     notifications: Benachrichtigungen
     preferences: Einstellungen
     settings: Einstellungen
-    two_factor_authentication: Zwei-Faktor-Authentisierung
+    two_factor_authentication: Zwei-Faktor-Auth
     your_apps: Deine Anwendungen
   statuses:
     open_in_web: Im Web öffnen
     over_character_limit: Zeichenlimit von %{max} überschritten
     pin_errors:
-      limit: Du kannst nicht noch mehr Beiträge anheften
+      limit: Du hast bereits die maximale Anzahl an Beiträgen angeheftet
       ownership: Du kannst nur eigene Beiträge anheften
       private: Du kannst nur öffentliche Beiträge anheften
       reblog: Du kannst keine geteilten Beiträge anheften
     show_more: Mehr anzeigen
+    title: '%{name}: "%{quote}"'
     visibilities:
       private: Nur Folgende
       private_long: Nur für Folgende sichtbar
@@ -603,7 +674,31 @@ de:
     recovery_instructions_html: Wenn du den Zugang zu deinem Telefon verlieren solltest, kannst du einen untenstehenden Wiederherstellungscodes benutzen, um wieder auf dein Konto zugreifen zu können. <strong>Bewahre die Wiederherstellungscodes gut auf.</strong> Du könntest sie beispielsweise ausdrucken und bei deinen restlichen wichtigen Dokumenten aufbewahren.
     setup: Einrichten
     wrong_code: Der eingegebene Code war ungültig! Stimmen Serverzeit und Gerätezeit?
+  user_mailer:
+    backup_ready:
+      explanation: Du hast ein vollständiges Backup von deinem Mastodon-Account angefragt. Es kann jetzt heruntergeladen werden!
+      subject: Dein Archiv ist bereit zum Download
+      title: Archiv-Download
+    welcome:
+      edit_profile_action: Profil einstellen
+      edit_profile_step: Du kannst dein Profil anpassen, indem du einen Avatar oder ein Titelbild hochlädst oder deinen Anzeigenamen änderst und mehr. Wenn du deine Follower vorher überprüfen möchtest, bevor sie dir folgen können, dann kannst du dein Profil sperren.
+      explanation: Hier sind ein paar Tipps, um loszulegen
+      final_action: Fang an zu posten
+      final_step: 'Fang an zu posten! Selbst ohne Follower werden deine öffentlichen Beitrage von anderen gesehen, zum Beispiel auf der lokalen Zeitleiste oder in Hashtags. Vielleicht möchtest du dich vorstellen mit dem #introductions-Hashtag.'
+      full_handle: Dein vollständiger Benutzername
+      full_handle_hint: Dies ist was du deinen Freunden sagen kannst, damit sie dich anschreiben oder von einer anderen Instanz folgen können.
+      review_preferences_action: Einstellungen ändern
+      review_preferences_step: Stelle sicher, dass du deine Einstellungen einstellst, wie zum Beispiel welche E-Mails du gerne erhalten möchtest oder was für Privatsphäreneinstellungen voreingestellt werden sollten. Wenn dir beim Ansehen von GIFs nicht schwindelig wird, dann kannst du auch das automatische Abspielen dieser aktivieren.
+      subject: Willkommen bei Mastodon
+      tip_bridge_html: Wenn du gerade von Twitter kommst, dann kannst du deine Freunde auf Mastodon mithilfe der <a href="%{bridge_url}">Bridge-App</a> finden. Es funktioniert aber auch nur, wenn diese die Bridge-App vorher verwendet haben!
+      tip_federated_timeline: Die föderierte Zeitleiste ist die sehr große Ansicht vom Mastodon-Netzwerk. Sie enthält aber auch nur Leute, denen du und deine Nachbarn folgen, sie ist also nicht komplett.
+      tip_following: Du folgst standardmäßig deinen Server-Admin(s). Um mehr interessante Leute zu finden, kannst du die lokale oder öffentliche Zeitleiste durchsuchen.
+      tip_local_timeline: Die lokale Zeitleiste ist eine Ansicht aller Leute auf %{instance}. Diese sind deine Nachbarn!
+      tip_mobile_webapp: Wenn dein mobiler Browser dir anbietet Mastodon zu deinem Startbildschirm hinzuzufügen, dann kannst du Benachrichtigungen erhalten. Es verhält sich wie eine native App in vielen Wegen!
+      tips: Tipps
+      title: Willkommen an Bord, %{name}!
   users:
     invalid_email: Ungültige E-Mail-Adresse
     invalid_otp_token: Ungültiger Zwei-Faktor-Authentisierungs-Code
+    seamless_external_login: Du bist angemeldet über einen Drittanbieter-Dienst, weswegen Passwort- und E-Maileinstellungen nicht verfügbar sind.
     signed_in_as: 'Angemeldet als:'
diff --git a/config/locales/devise.ar.yml b/config/locales/devise.ar.yml
index 28a03ff12..4e302d3ea 100644
--- a/config/locales/devise.ar.yml
+++ b/config/locales/devise.ar.yml
@@ -17,30 +17,66 @@ ar:
       unconfirmed: يجب عليك تأكيد عنوان بريدك الإلكتروني قبل المواصلة.
     mailer:
       confirmation_instructions:
+        action: للتحقق من عنوان البريد الإلكتروني
+        explanation: لقد قمت بإنشاء حساب على %{host} بواسطة عنوان البريد الإلكتروني الحالي. إنك على بعد خطوات قليلة من تفعليه. إن لم تكن من طلب ذلك، يرجى ألّا تولي إهتماما بهذه الرسالة.
+        extra_html: ندعوك إلى الإطلاع على <a href="%{terms_path}">القواعد الخاصة بمثيل الخادوم هذا</a> and <a href="%{policy_path}">و شروط الخدمة الخاصة بنا</a>.
         subject: 'ماستدون : تعليمات التأكيد لمثيل الخادوم  %{instance}'
+        title: للتحقق من عنوان البريد الإلكتروني
+      email_changed:
+        explanation: 'لقد تم تغيير عنوان البريد الإلكتروني الخاص بحسابك إلى :'
+        extra: إن لم تقم شخصيًا بتعديل عنوان بريدك الإلكتروني ، ذلك يعني أنّ شخصا آخر قد نَفِذَ إلى حسابك. فالرجاء قم بتعديل كلمتك السرية في الحال أو قم بالإتصال بمدير مثيل الخادوم إن كنت غير قادر على استعمال حسابك.
+        subject: 'ماستدون : تم استبدال عنوان بريدك الإلكتروني'
+        title: عنوان البريد الإلكتروني الجديد
       password_change:
+        explanation: تم تغيير كلمة السر الخاصة بحسابك.
+        extra: إن لم تقم شخصيًا بتعديل كلمتك السرية، ذلك يعني أنّ شخصا آخر قد سيطر على حسابك. فالرجاء قم بتعديل كلمتك السرية في الحال أو قم بالإتصال بمدير مثيل الخادوم إن كنت غير قادر على استعمال حسابك.
         subject: 'ماستدون : تم تغيير كلمة المرور'
+        title: تم تغيير كلمة السر
+      reconfirmation_instructions:
+        explanation: ندعوك لتأكيد العنوان الجديد قصد تعديله في بريدك.
+        extra: إن لم تكن صاحب هذا الطلب ، يُرجى عدم إعارة الإهتمام لهذه الرسالة. فعنوان البريد الإلكتروني المتعلق بحساب ماستدون سوف يبقى هو مِن غير أي تعديل إلّا و فقط إن قمت بالنقر على الرابط أعلاه قصد تعديله.
+        subject: 'ماستدون : تأكيد كلمة السر الخاصة بـ %{instance}'
+        title: التحقق من عنوان البريد الإلكتروني
       reset_password_instructions:
+        action: تغيير كلمة السر
+        explanation: لقد قمت بطلب تغيير كلمة السر الخاصة بحسابك.
+        extra: إن لم تكن صاحب هذا الطلب ، يُرجى عدم إعارة الإهتمام لهذه الرسالة. فكلِمَتُك السرية تبقى هي مِن غير أي تعديل إلّا و فقط إن قمت بالنقر على الرابط أعلاه قصد إنشاء كلمة سرية جديدة.
         subject: 'ماستدون : تعليمات إستعادة كلمة المرور'
+        title: إعادة تعيين كلمة السر
       unlock_instructions:
         subject: 'ماستدون : تعليمات فك القفل'
     omniauth_callbacks:
       failure: تعذرت المصادقة من %{kind} بسبب "%{reason}".
       success: تمت المصادقة بنجاح عبر حساب %{kind}.
     passwords:
+      no_token: ليس بإمكانك النفاذ إلى هذه الصفحة  إن لم تقم بالنقر على الرابط المتواجد في الرسالة الإلكترونية. الرجاء التحقق مِن أنك قمت بإدخال عنوان الرابط كاملا كما هو مذكور في رسالة إعادة تعيين الكلمة السرية.
+      send_instructions: إن كان عنوان بريدك الإلكتروني ضمن قاعدة بياناتنا، فسوف تتلقّى في غضون دقائق رابطا يُمكّنُك مِن استعادة كلمتك السرية على عنوان علبة البريد الإلكتروني الخاصة بك.إن لم تجد هذه الرسالة، يرجى تفقد مجلّد البريد المزعج.
+      send_paranoid_instructions: إن كان عنوان بريدك الإلكتروني ضمن قاعدة بياناتنا، فسوف تتلقّى في غضون دقائق رابطا يُمكّنُك مِن استعادة كلمتك السرية على عنوان علبة البريد الإلكتروني الخاصة بك.إن لم تجد هذه الرسالة، يرجى تفقد مجلّد البريد المزعج.
       updated: تم تغيير كلمة المرور بنجاح. أنت مسجل الآن.
       updated_not_active: تم تغيير كلمة المرور بنجاح.
     registrations:
       destroyed: إلى اللقاء ! لقد تم إلغاء حسابك. نتمنى أن نراك مجددا.
       signed_up: أهلا وسهلا ! تم تسجيل دخولك بنجاح.
       signed_up_but_inactive: لقد تمت عملية إنشاء حسابك بنجاح إلاّ أنه لا يمكننا تسجيل دخولك إلاّ بعد قيامك بتفعيله.
+      signed_up_but_locked: لقد تم تسجيل حسابك بنجاح إلّا أنه لا يمكنك تسجيل الدخول لأن حسابك مجمد.
       signed_up_but_unconfirmed: لقد تم إرسال رسالة تحتوي على رابط للتفعيل إلى عنوان بريدك الإلكتروني. بالضغط على الرابط سوف يتم تفعيل حسابك. لذا يُرجى إلقاء نظرة على ملف الرسائل غير المرغوب فيها إنْ لم تَعثُر على الرسالة السالفة الذِكر.
+      update_needs_confirmation: لقد قمت بتحديث حسابك بنجاح إلا أنه يجب علينا التأكد من صحة عنوان بريدك الإلكتروني الجديد. يرجى الإطلاع على بريدك و اتباع الرابط الذي تلقيتَه لتأكيد عنوان بريدك الإلكتروني الجديد. إن لم تتلقى تلك الرسالة ، ندعوك إلى تفقُّد مجلد البريد المزعج.
       updated: تم تحديث حسابك بنجاح.
     sessions:
       already_signed_out: تم تسجيل خروجك بنجاح.
       signed_in: تم تسجيل دخولك بنجاح.
       signed_out: تم تسجيل خروجك بنجاح.
+    unlocks:
+      send_instructions: سوف تتلقى خلال بضع دقائق رسالة إلكترونية تحتوي على التعليمات اللازمة لفك القفل عن حسابك. إن لم تتلقى تلك الرسالة ، ندعوك إلى تفقُّد مجلد البريد المزعج.
+      send_paranoid_instructions: إن كان حسابك موجود فعليًا فسوف تتلقى في غضون دقائق رسالة إلكترونية تحتوي على تعليمات تدُلُّك على كيفية فك القفل عن حسابك. إن لم تتلقى تلك الرسالة ، ندعوك إلى تفقُّد مجلد البريد المزعج.
+      unlocked: لقد تمت عملية إلغاء تجميد حسابك بنجاح. للمواصلة،  يُرجى تسجيل الدخول.
   errors:
     messages:
+      already_confirmed: قمت بتأكيده من قبل، يرجى إعادة محاولة تسجيل الدخول
+      confirmation_period_expired: يجب التأكد منه قبل انقضاء مدة %{period}، يرجى إعادة طلب جديد
+      expired: إنتهت مدة صلاحيته، الرجاء طلب واحد جديد
       not_found: لا يوجد
       not_locked: ليس مقفلاً
+      not_saved:
+        one: 'خطأ واحد منَعَ %{resource} مِن القيام بالإحتفاظ :'
+        other: "%{count} أخطاء منعت %{resource} مِن القيام بالإحتفاظ :"
diff --git a/config/locales/devise.ca.yml b/config/locales/devise.ca.yml
index d88db17ca..808a5dd0a 100644
--- a/config/locales/devise.ca.yml
+++ b/config/locales/devise.ca.yml
@@ -7,29 +7,29 @@ ca:
       send_paranoid_instructions: Si l'adreça de correu electrònic existeix en la nostra base de dades, en pocs minuts rebràs un correu electrònic amb instruccions sobre com confirmar l'adreça de correu.
     failure:
       already_authenticated: Ja estàs registrat.
-      inactive: el teu compte encara no s'ha activat.
+      inactive: El teu compte encara no s'ha activat.
       invalid: "%{authentication_keys} o contrasenya no són vàlids."
       last_attempt: Tens un intent més, abans que es bloqui el compte.
-      locked: el compte s'ha blocat.
+      locked: El compte s'ha blocat.
       not_found_in_database: "%{authentication_keys} o contrasenya no vàlids."
-      timeout: la sessió ha expirat. Incia sessió una altra vegada per a continuar.
+      timeout: La sessió ha expirat. Inicia sessió una altra vegada per a continuar.
       unauthenticated: Cal iniciar sessió o registrar-se abans de continuar.
       unconfirmed: Has de confirmar l'adreça de correu electrònic abans de continuar.
     mailer:
       confirmation_instructions:
-        action: Verificar l'adreça de correu
+        action: Verifica l'adreça de correu
         explanation: Has creat un compte a %{host} amb aquesta adreça de correu electrònic. Estàs a un sol clic de l'activació. Si no fos així, ignora aquest correu electrònic.
         extra_html: Si us plau consulta també <a href="%{terms_path}"> les regles de la instància</a> i <a href="%{policy_path}"> les nostres condicions de servei</a>.
         subject: 'Mastodon: Instruccions de confirmació'
         title: Verifica l'adreça de correu
       email_changed:
         explanation: 'L''adreça de correu del teu compte s''està canviant a:'
-        extra: Si no has canviat el teu correu electrònic, és probable que algú hagi accedit al teu compte. Si us plau, canvia la contrasenya immediatament o posa't en contacte amb l'administrador de l'instància si no pots accedir al teu compte.
+        extra: Si no has canviat el teu correu electrònic, és probable que algú hagi accedit al teu compte. Si us plau, canvia la contrasenya immediatament o posa't en contacte amb l'administrador de la instància si no pots accedir al teu compte.
         subject: 'Mastodon: s''ha canviat l''adreça electrònica'
-        title: Nova adreça de correu electrònic
+        title: Adreça de correu electrònic nova
       password_change:
         explanation: S'ha canviat la contrasenya del teu compte.
-        extra: Si no has canviat el teu correu electrònic, és probable que algú hagi accedit al teu compte. Si us plau, canvia la contrasenya immediatament o posa't en contacte amb l'administrador de l'instància si no pots accedir al teu compte.
+        extra: Si no has canviat el teu correu electrònic, és probable que algú hagi accedit al teu compte. Si us plau, canvia la contrasenya immediatament o posa't en contacte amb l'administrador de la instància si no pots accedir al teu compte.
         subject: 'Mastodon: Contrasenya canviada'
         title: Contrasenya canviada
       reconfirmation_instructions:
diff --git a/config/locales/devise.de.yml b/config/locales/devise.de.yml
index 6154231c7..77243ba15 100644
--- a/config/locales/devise.de.yml
+++ b/config/locales/devise.de.yml
@@ -12,16 +12,37 @@ de:
       last_attempt: Du hast noch einen Versuch, bevor dein Konto gesperrt wird.
       locked: Dein Konto ist gesperrt.
       not_found_in_database: "%{authentication_keys} oder Passwort ungültig."
-      timeout: Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.
+      timeout: Deine Sitzung ist abgelaufen. Bitte melde dich erneut an, um fortzufahren.
       unauthenticated: Du musst dich anmelden oder registrieren, bevor du fortfahren kannst.
       unconfirmed: Du musst deine E-Mail-Adresse bestätigen, bevor du fortfahren kannst.
     mailer:
       confirmation_instructions:
+        action: E-Mail-Adresse verifizieren
+        explanation: Du hast einen Account auf %{host} mit dieser E-Mail-Adresse erstellt. Du bist nun einen Klick entfernt vor der Aktivierung. Wenn du das nicht warst, kannst du diese E-Mail ignorieren.
+        extra_html: Bitte lies auch die <a href="%{terms_path}">Regeln dieser Instanz</a> und <a href="%{policy_path}">unsere Nutzungsbedingungen</a>.
         subject: 'Mastodon: Bestätigung deines Kontos bei %{instance}'
+        title: Verifiziere E-Mail-Adresse
+      email_changed:
+        explanation: 'Die E-Mail-Adresse deines Accounts wird geändert zu:'
+        extra: Wenn du deine E-Mail-Adresse nicht geändert hast, dann kann es vermutlich sein, dass jemand Zugriff zu deinem Account erhalten hat. Bitte ändere sofort dein Passwort oder kontaktiere den Administrator deiner Instanz, wenn du dich ausgesperrt hast.
+        subject: 'Mastodon: E-Mail-Adresse geändert'
+        title: Neue E-Mail-Adresse
       password_change:
+        explanation: Das Passwort für deinen Account wurde geändert.
+        extra: Wenn du dein Passwort nicht geändert hast, dann kann es vermutlich sein, dass jemand Zugriff zu deinem Account erhalten hat. Bitte ändere sofort dein Passwort oder kontaktiere den Administrator deiner Instanz, wenn du dich ausgesperrt hast.
         subject: 'Mastodon: Passwort geändert'
+        title: Passwort geändert
+      reconfirmation_instructions:
+        explanation: Bestätige deine neue E-Mail-Adresse, um sie zu ändern.
+        extra: Wenn diese Änderung nicht von dir angestoßen wurde, dann solltest du diese E-Mail ignorieren. Die E-Mail-Adresse für deinen Mastodon-Account wird sich nicht ändern, bis du den obigen Link anklickst.
+        subject: 'Mastodon: Bestätige E-Mail-Adresse für %{instance}'
+        title: Verifiziere E-Mail-Adresse
       reset_password_instructions:
+        action: Ändere Passwort
+        explanation: Du hast ein neues Passwort für deinen Account angefragt.
+        extra: Wenn du diese Anfrage nicht gestellt hast, solltest du diese E-Mail ignorieren. Dein Passwort wird sich nicht ändern solange du den obigen Link anklickst und ein neues erstellst.
         subject: 'Mastodon: Passwort zurücksetzen'
+        title: Passwort zurücksetzen
       unlock_instructions:
         subject: 'Mastodon: Konto entsperren'
     omniauth_callbacks:
@@ -35,7 +56,7 @@ de:
       updated_not_active: Dein Passwort wurde geändert.
     registrations:
       destroyed: Dein Konto wurde gelöscht.
-      signed_up: Du hast dich erfolgreich registriert.
+      signed_up: Willkommen! Du hast dich erfolgreich registriert.
       signed_up_but_inactive: Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Konto inaktiv ist.
       signed_up_but_locked: Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Konto gesperrt ist.
       signed_up_but_unconfirmed: Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Konto noch nicht bestätigt ist. Du erhältst in Kürze eine E-Mail. Darin ist erklärt, wie du dein Konto freischalten kannst.
@@ -51,7 +72,7 @@ de:
       unlocked: Dein Konto wurde entsperrt. Du bist jetzt angemeldet.
   errors:
     messages:
-      already_confirmed: wurde bereits bestätigt.
+      already_confirmed: wurde bereits bestätigt, bitte versuche dich anzumelden
       confirmation_period_expired: muss innerhalb %{period} bestätigt werden, bitte fordere einen neuen Link an.
       expired: ist abgelaufen, bitte neu anfordern.
       not_found: wurde nicht gefunden.
diff --git a/config/locales/devise.eo.yml b/config/locales/devise.eo.yml
index a2303ec1e..71dd6c1ef 100644
--- a/config/locales/devise.eo.yml
+++ b/config/locales/devise.eo.yml
@@ -2,60 +2,81 @@
 eo:
   devise:
     confirmations:
-      confirmed: Via konto estas konfirmita.
-      send_instructions: Vi ricevos instrukciojn por konfirmi vian konton post kelkaj minutoj.
-      send_paranoid_instructions: Se via retpoŝt-adreso ekzistas en nia datumbazo, vi baldaŭ ricevos retpoŝt-mesaĝon, kiu enhavas la instrukciojn por konfirmi vian konton.
+      confirmed: Via retadreso estis sukcese konfirmita.
+      send_instructions: Vi ricevos retmesaĝon kun instrukcioj por konfirmi vian retadreson ene de kelkaj minutoj. Bonvolu kontroli vian spamujon se vi ne ricevis ĉi tiun retmesaĝon.
+      send_paranoid_instructions: Se via retadreso ekzistas en nia datumbazo, vi ricevos retmesaĝon kun instrukcioj por konfirmi vian retadreson ene de kelkaj minutoj. Bonvolu kontroli vian spamujon se vi ne ricevis ĉi tiun retmesaĝon.
     failure:
-      already_authenticated: Vi jam estas ensalutita.
+      already_authenticated: Vi jam ensalutis.
       inactive: Via konto ankoraŭ ne estas konfirmita.
-      invalid: Malĝusta retpoŝt-adreso aŭ pasvorto.
+      invalid: Nevalida %{authentication_keys} aŭ pasvorto.
       last_attempt: Vi ankoraŭ povas provi unufoje antaŭ ol via konto estos ŝlosita.
       locked: Via konto estas ŝlosita.
-      not_found_in_database: Malĝusta retpoŝt-adreso aŭ pasvorto.
-      timeout: Via sesio eksiĝis. Bonvolu reensaluti por daŭrigi.
-      unauthenticated: Vi devas ensaluti aŭ membriĝi por daŭrigi.
-      unconfirmed: Vi devas konfirmi vian konton por daŭrigi.
+      not_found_in_database: Nevalida %{authentication_keys} aŭ pasvorto.
+      timeout: Via seanco eksvalidiĝis. Bonvolu ensaluti denove por daŭrigi.
+      unauthenticated: Vi devas ensaluti aŭ registriĝi antaŭ ol daŭrigi.
+      unconfirmed: Vi devas konfirmi vian retadreson antaŭ ol daŭrigi.
     mailer:
       confirmation_instructions:
-        subject: Instrukcioj por konfirmi
+        action: Konfirmi retadreson
+        explanation: Vi kreis konton en %{host} per ĉi tiu retadreso. Nur klako restas por aktivigi ĝin. Se tio ne estis vi, bonvolu ignori ĉi tiun retmesaĝon.
+        extra_html: Bonvolu rigardi <a href="%{terms_path}">la regulojn de la nodo</a> kaj <a href="%{policy_path}">niajn uzkondiĉojn</a>.
+        subject: 'Mastodon: Konfirmaj instrukcioj por %{instance}'
+        title: Konfirmi retadreson
+      email_changed:
+        explanation: 'La retadreso de via konto ŝanĝiĝas al:'
+        extra: Se vi ne volis ŝanĝi vian retadreson, iu verŝajne aliris al via konto. Bonvolu tuj ŝanĝi vian pasvorton aŭ kontakti la administranton de la nodo, se vi estas blokita ekster via konto.
+        subject: 'Mastodon: Retadreso ŝanĝita'
+        title: Nova retadreso
       password_change:
-        subject: Via pasvorto estis ŝanĝita senprobleme.
+        explanation: La pasvorto de via konto estis ŝanĝita.
+        extra: Se vi ne ŝanĝis vian pasvorton, iu verŝajne aliris al via konto. Bonvolu ŝanĝi vian pasvorton tuj aŭ kontakti la administranton de la nodo, se vi estas blokita ekster via konto.
+        subject: 'Mastodon: Pasvorto ŝanĝita'
+        title: Pasvorto ŝanĝita
+      reconfirmation_instructions:
+        explanation: Retajpu la novan adreson por ŝanĝi vian retadreson.
+        extra: Se ĉi tiu ŝanĝo ne estis komencita de vi, bonvolu ignori ĉi tiun retmesaĝon. La retadreso por la Mastodon-konto ne ŝanĝiĝos se vi ne aliras la supran ligilon.
+        subject: 'Mastodon: Konfirmi retadreson por %{instance}'
+        title: Kontrolu retadreson
       reset_password_instructions:
-        subject: Instrukcioj por ŝanĝi la pasvorton
+        action: Ŝanĝi pasvorton
+        explanation: Vi petis novan pasvorton por via konto.
+        extra: Se vi ne petis ĉi tion, bonvolu ignori ĉi tiun retmesaĝon. Via pasvorto ne ŝanĝiĝos se vi ne aliras la supran ligilon kaj kreas novan.
+        subject: 'Mastodon: Instrukcioj por ŝanĝi pasvorton'
+        title: Pasvorto restarigita
       unlock_instructions:
-        subject: Instrukcioj por malŝlosi la konton
+        subject: 'Mastodon: Instrukcioj por malŝlosi'
     omniauth_callbacks:
       failure: 'Ni ne povis aŭtentigi vin per %{kind}: ''%{reason}''.'
-      success: Aŭtentigita senprobleme per %{kind}.
+      success: Aŭtentigita sukcese per %{kind}.
     passwords:
-      no_token: Vi ne povas iri al tiu paĝo per alia vojo ol retpoŝt-mesaĝo por ŝanĝi pasvorton. Se vi venas de tia retpoŝt-mesaĝo, kontrolu ke vi uzis la tutan URL.
-      send_instructions: Vi ricevos retpoŝt-mesaĝon kun instrukcioj por ŝanĝi vian pasvorton post kelkaj minutoj.
-      send_paranoid_instructions: Se via retpoŝt-adreso ekzistas en nia datumbazo, vi ricevos ligilon por ŝanĝi vian pasvorton per retpoŝt-mesaĝo.
-      updated: Via pasvorto estis redaktita senprobleme, vi nun estas ensalutita.
-      updated_not_active: Via pasvorto estis redaktita senprobleme.
+      no_token: Vi ne povas aliri al ĉi tiu paĝo alimaniere ol per retmesaĝo por ŝanĝi pasvorton. Se vi venas de tia retmesaĝo, bonvolu certiĝi, ke vi uzas la tutan URL-on donitan.
+      send_instructions: Se via retadreso ekzistas en nia datumbazo, vi ricevos ligilon por havi novan pasvorton ĉe via retadreso ene de kelkaj minutoj. Bonvolu kontroli vian spamujon se vi ne ricevis ĉi tiun retmesaĝon.
+      send_paranoid_instructions: Se via retadreso ekzistas en nia datumbazo, vi ricevos ligilon por havi novan pasvorton ĉe via retadreso ene de kelkaj minutoj. Bonvolu kontroli vian spamujon se vi ne ricevis ĉi tiun retmesaĝon.
+      updated: Via pasvorto estis sukcese ŝanĝita. Vi nun estas ensalutinta.
+      updated_not_active: Via pasvorto estis sukcese ŝanĝita.
     registrations:
-      destroyed: Ĝis! Via konto estis forigita senprobleme. Ni esperas revidi vin baldaŭ.
-      signed_up: Bonvenon! Vi membriĝis senprobleme.
-      signed_up_but_inactive: Vi bone membriĝis, sed vi ankoraŭ ne povas ensaluti ĉar via konto ne estis konfirmita.
-      signed_up_but_locked: Vi bone membriĝis, sed vi ne povas ensaluti ĉar via konto estas ŝlosita.
-      signed_up_but_unconfirmed: Retpoŝt-mesaĝo kun via ligilo por konfirmi vian konton estis sendita al via retpoŝt-adreso. Bonvolu uzi tiun ligilon por konfirmi vian konton.
-      update_needs_confirmation: Vi bone aktualigis vian konton, sed ni bezonas kontroli vian novan retpoŝt-adreson. Bonvolu kontroli viajn retpoŝt-mesaĝojn kaj uzi la ligilon por konfirmi vian novan retpoŝt-adreson.
-      updated: Via konto estis aktualigita senprobleme.
+      destroyed: Ĝis! Via konto estis sukcese forigita. Ni esperas revidi vin baldaŭ.
+      signed_up: Bonvenon! Vi sukcese registriĝis.
+      signed_up_but_inactive: Vi sukcese registriĝis. Tamen, ni ne povis ensalutigi vin, ĉar via konto ankoraŭ ne estas konfirmita.
+      signed_up_but_locked: Vi sukcese registriĝis. Tamen, ni ne povis ensalutigi vin, ĉar via konto estas ŝlosita.
+      signed_up_but_unconfirmed: Retmesaĝo kun konfirma ligilo estis sendita al via retadreso. Bonvolu sekvi la ligilon por aktivigi vian konton. Bonvolu kontroli vian spamujon, se vi ne ricevis ĉi tiun retmesaĝon.
+      update_needs_confirmation: Vi sukcese ĝisdatigis vian konton, sed ni bezonas kontroli vian novan retadreson. Bonvolu kontroli viajn retmesaĝojn kaj sekvi la konfirman ligilon por konfirmi vian novan retadreson. Bonvolu kontroli vian spamujon, se vi ne ricevis ĉi tiun retmesaĝon.
+      updated: Via konto estis sukcese ĝisdatigita.
     sessions:
-      already_signed_out: Elsalutita.
-      signed_in: Ensalutita.
-      signed_out: Elsalutita.
+      already_signed_out: Sukcese elsalutis.
+      signed_in: Sukcese ensalutis.
+      signed_out: Sukcese elsalutis.
     unlocks:
-      send_instructions: Vi ricevos retpoŝt-mesaĝon kun instrukcioj por malŝlosi vian konton post kelkaj minutoj.
-      send_paranoid_instructions: Se via retpoŝt-adreso ekzistas en nia datumbazo, vi ricevos ligilon por malŝlosi vian konton per retpoŝt-mesaĝo.
-      unlocked: Via konto estis malŝlosita senprobleme, vi nun estas ensalutita.
+      send_instructions: Vi ricevos retmesaĝon kun instrukcioj por malŝlosi vian konton ene de kelkaj minutoj. Bonvolu kontroli vian spamujon, se vi ne ricevis ĉi tiun retmesaĝon.
+      send_paranoid_instructions: Se via konto ekzistas, vi ricevos retmesaĝon kun instrukcioj por malŝlosi ĝin ene de kelkaj minutoj. Bonvolu kontroli vian spamujon se vi ne ricevis ĉi tiun retmesaĝon.
+      unlocked: Via konto estis sukcese malŝlosita. Bonvolu ensaluti por daŭrigi.
   errors:
     messages:
       already_confirmed: jam estis konfirmita, bonvolu provi ensaluti
-      confirmation_period_expired: devas esti konfirmita en %{period}, bonvolu repeti
-      expired: eksiĝis, bonvolu repeti
+      confirmation_period_expired: devas esti konfirmita ene de %{period}, bonvolu peti denove
+      expired: eksvalidiĝis, bonvolu peti denove
       not_found: ne estis trovita
       not_locked: ne estis ŝlosita
       not_saved:
-        one: '1 eraro malpermesis al tiu %{resource} esti konservita:'
-        other: "%{count} eraroj malpermesis al tiu %{resource} esti konservita:"
+        one: '1 eraro malpermesis al ĉi tiu %{resource} esti konservita:'
+        other: "%{count} eraroj malpermesis al ĉi tiu %{resource} esti konservita:"
diff --git a/config/locales/devise.es.yml b/config/locales/devise.es.yml
index 5a689f347..ecb97fd13 100644
--- a/config/locales/devise.es.yml
+++ b/config/locales/devise.es.yml
@@ -4,24 +4,45 @@ es:
     confirmations:
       confirmed: Su dirección de correo ha sido confirmada con éxito.
       send_instructions: Recibirá un correo electrónico con instrucciones sobre cómo confirmar su dirección de correo en pocos minutos.
-      send_paranoid_instructions: Si su dirección de correo electrónico existe en nuesta base de datos, recibirá un correo electrónico con instrucciones sobre cómo confirmar su dirección de correo en pocos minutos.
+      send_paranoid_instructions: Si su dirección de correo electrónico existe en nuestra base de datos, recibirá un correo electrónico con instrucciones sobre cómo confirmar su dirección de correo en pocos minutos.
     failure:
       already_authenticated: Usted ya está registrado.
       inactive: Su cuenta todavía no está activa.
       invalid: Inválido %{authentication_keys} o contraseña.
       last_attempt: Tiene un intento más antes de que su cuenta sea bloqueada.
-      locked: su cuenta está bloqueada.
+      locked: Su cuenta está bloqueada.
       not_found_in_database: Inválido %{authentication_keys} o contraseña.
-      timeout: su sesión ha expirado. Por favor inicie sesión de nuevo para continuar.
+      timeout: Su sesión ha expirado. Por favor inicie sesión de nuevo para continuar.
       unauthenticated: Necesita iniciar sesión o registrarse antes de continuar.
-      unconfirmed: Tiene que confirmar su correo electrónico antes de continuar.
+      unconfirmed: Tiene que confirmar su dirección de correo electrónico antes de continuar.
     mailer:
       confirmation_instructions:
-        subject: 'Mastodon: Instrucciones de confirmación'
+        action: Verificar dirección de correo electrónico
+        explanation: Has creado una cuenta en %{host} con esta dirección de correo electrónico. Estas a un clic de activarla. Si no fue usted, por favor ignore este correo electrónico.
+        extra_html: Por favor revise <a href="%{terms_path}">las reglas de la instancia</a> y <a href="%{policy_path}">nuestros términos de servicio</a>.
+        subject: 'Mastodon: Instrucciones de confirmación para %{instance}'
+        title: Verificar dirección de correo electrónico
+      email_changed:
+        explanation: 'El correo electrónico para su cuenta esta siendo cambiada a:'
+        extra: Si usted no a cambiado su correo electrónico. es probable que alguien a conseguido acceso a su cuenta. Por favor cambie su contraseña inmediatamente o contacte a el administrador de la instancia si usted esta bloqueado de su cuenta.
+        subject: 'Mastodon: Correo electrónico cambiado'
+        title: Nueva dirección de correo electrónico
       password_change:
+        explanation: La contraseña de su cuenta a sido cambiada.
+        extra: Si usted no a cambiado su contraseña. es probable que alguien a conseguido acceso a su cuenta. Por favor cambie su contraseña inmediatamente o contacte a el administrador de la instancia si usted esta bloqueado de su cuenta.
         subject: 'Mastodon: Contraseña cambiada'
+        title: Contraseña cambiada
+      reconfirmation_instructions:
+        explanation: Confirme la nueva dirección para cambiar su coreo electrónico.
+        extra: Si no iniciaste este cambio, por favor ignora este correo. Esta dirección de correo para la cuenta de Mastodon no cambiará hasta que accedas al vinculo arriba.
+        subject: 'Mastodon: Confirme correo electrónico para %{instance}'
+        title: Verifique dirección de correo electrónico
       reset_password_instructions:
+        action: Cambiar contraseña
+        explanation: Solicitaste una nueva contraseña para tu cuenta.
+        extra: Si no solicitaste esto, por favor ignora este correo. Tu contraseña no cambiará hasta que tu accedas al vinculo arriba y crees una nueva.
         subject: 'Mastodon: Instrucciones para reiniciar contraseña'
+        title: Reiniciar contraseña
       unlock_instructions:
         subject: 'Mastodon: Instrucciones para desbloquear'
     omniauth_callbacks:
diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml
index a9817044d..7e10f83b4 100644
--- a/config/locales/devise.fr.yml
+++ b/config/locales/devise.fr.yml
@@ -6,7 +6,7 @@ fr:
       send_instructions: Vous allez recevoir les instructions nécessaires à la confirmation de votre compte dans quelques minutes. Veuillez, dans le cas où vous ne recevriez pas ce message, vérifier votre dossier d’indésirables.
       send_paranoid_instructions: Si votre adresse électronique existe dans notre base de données, vous allez bientôt recevoir un courriel contenant les instructions de confirmation de votre compte. Veuillez, dans le cas où vous ne recevriez pas ce message, vérifier votre dossier d’indésirables.
     failure:
-      already_authenticated: Vous êtes déjà connecté⋅e
+      already_authenticated: Vous êtes déjà connecté⋅e.
       inactive: Votre compte n’est pas encore activé.
       invalid: Courriel ou mot de passe incorrect.
       last_attempt: Vous avez droit à une tentative avant que votre compte ne soit verrouillé.
@@ -17,11 +17,32 @@ fr:
       unconfirmed: Vous devez valider votre compte pour continuer.
     mailer:
       confirmation_instructions:
+        action: Vérifier l'adresse courriel
+        explanation: Vous avez créé un compte sur %{host} avec cette adresse courriel. Vous êtes à un clic de l'activer. Si ce n'était pas vous, veuillez ignorer ce courriel.
+        extra_html: S'il vous plaît, consultez également <a href="%{terms_path}"> 1les règles de l'instance</a> 2 et <a href="%{policy_path}">3nos termes de service</a> 4.
         subject: Merci de confirmer votre inscription sur %{instance}
+        title: Vérifier l'adresse courriel
+      email_changed:
+        explanation: 'L''adresse courriel de votre compte est en cours de modification pour devenir :'
+        extra: Si vous n'avez pas changé votre adresse courriel, il est probable que quelqu'un ait eu accès à votre compte. Veuillez changer votre mot de passe immédiatement ou contacter l'administrateur de l'instance si vous êtes bloqué hors de votre compte.
+        subject: 'Mastodon : Courriel modifié'
+        title: Nouvelle adresse courriel
       password_change:
-        subject: Votre mot de passe a été modifié avec succès.
+        explanation: Le mot de passe de votre compte a été changé.
+        extra: Si vous n'avez pas changé votre mot de passe, il est probable que quelqu'un ait eu accès à votre compte. Veuillez changer votre mot de passe immédiatement ou contacter l'administrateur de l'instance si vous êtes bloqué hors de votre compte.
+        subject: Votre mot de passe a été modifié avec succès
+        title: Mot de passe modifié
+      reconfirmation_instructions:
+        explanation: Confirmez la nouvelle adresse pour changer votre courriel.
+        extra: Si ce changement n' a pas été initié par vous, veuillez ignorer ce courriel. L'adresse courriel du compte Mastodon ne changera pas tant que vous n'aurez pas cliqué sur le lien ci-dessus.
+        subject: 'Mastodon : Confirmez l''email pour %{instance}'
+        title: Vérifier l'adresse courriel
       reset_password_instructions:
+        action: Modifier le mot de passe
+        explanation: Vous avez demandé un nouveau mot de passe pour votre compte.
+        extra: Si vous ne l'avez pas demandé, veuillez ignorer ce courriel. Votre mot de passe ne changera pas tant que vous n'aurez pas cliqué sur le lien ci-dessus et que vous n'en aurez pas créé un nouveau.
         subject: Instructions pour changer votre mot de passe
+        title: Réinitialisation du mot de passe
       unlock_instructions:
         subject: Instructions pour déverrouiller votre compte
     omniauth_callbacks:
@@ -31,7 +52,7 @@ fr:
       no_token: Vous ne pouvez accéder à cette page sans passer par un courriel de réinitialisation de mot de passe. Si vous êtes passé⋅e par un courriel de ce type, assurez-vous d’utiliser l’URL complète.
       send_instructions: Vous allez recevoir les instructions de réinitialisation du mot de passe dans quelques instants. Veuillez, dans le cas où vous ne recevriez pas ce message, vérifier votre dossier d’indésirables.
       send_paranoid_instructions: Si votre adresse électronique existe dans notre base de données, vous allez recevoir un lien de réinitialisation par courriel. Veuillez, dans le cas où vous ne recevriez pas ce message, vérifier votre dossier d’indésirables.
-      updated: Votre mot de passe a été modifié avec succès, vous êtes maintenant connecté⋅e
+      updated: Votre mot de passe a été modifié avec succès, vous êtes maintenant connecté⋅e.
       updated_not_active: Votre mot de passe a été modifié avec succès.
     registrations:
       destroyed: Votre compte a été supprimé avec succès. Nous espérons vous revoir bientôt.
@@ -42,16 +63,16 @@ fr:
       update_needs_confirmation: Votre compte a bien été mis à jour mais nous devons vérifier votre nouvelle adresse courriel. Merci de vérifier vos courriels et de cliquer sur le lien de confirmation pour finaliser la validation de votre nouvelle adresse.
       updated: Votre compte a été modifié avec succès.
     sessions:
-      already_signed_out: Déconnecté.
-      signed_in: Connecté.
-      signed_out: Déconnecté.
+      already_signed_out: Déconnecté·e.
+      signed_in: Connecté·e.
+      signed_out: Déconnecté·e.
     unlocks:
       send_instructions: Vous allez recevoir les instructions nécessaires au déverrouillage de votre compte dans quelques instants. Veuillez, dans le cas où vous ne recevriez pas ce message, vérifier votre dossier d’indésirables.
       send_paranoid_instructions: Si votre compte existe, vous allez bientôt recevoir un courriel contenant les instructions pour le déverrouiller. Veuillez, dans le cas où vous ne recevriez pas ce message, vérifier votre dossier d’indésirables.
       unlocked: Votre compte a été déverrouillé avec succès, vous êtes maintenant connecté⋅e.
   errors:
     messages:
-      already_confirmed: a déjà été validé⋅e, veuillez essayer de vous connecter
+      already_confirmed: a déjà été validée, veuillez essayer de vous connecter
       confirmation_period_expired: à confirmer dans les %{period}, merci de faire une nouvelle demande
       expired: a expiré, merci d’en faire une nouvelle demande
       not_found: n’a pas été trouvé⋅e
diff --git a/config/locales/devise.gl.yml b/config/locales/devise.gl.yml
index c0fc53834..a8fe6a2a5 100644
--- a/config/locales/devise.gl.yml
+++ b/config/locales/devise.gl.yml
@@ -17,15 +17,32 @@ gl:
       unconfirmed: Debe confirmar o seu enderezo de correo antes de continuar.
     mailer:
       confirmation_instructions:
+        action: Validar enderezo de correo-e
+        explanation: Creou unha conta en %{host} con este enderezo de correo. Está a punto de activalo, si non foi vostede quen fixo a petición, por favor ignore este correo.
+        extra_html: Por favor, lea tamén <a href="%{terms_path}">as normas da instancia</a> e <a href="%{policy_path}">os termos do servizo</a>.
         subject: 'Mastodon: Instruccións de confirmación para %{instance}'
+        title: Verificar enderezo de correo-e
       email_changed:
+        explanation: 'O seu enderezo de correo para esta conta foi cambiado a:'
+        extra: Si non fixo a petición de cambio de correo-e é probable que alguén obtivese acceso a súa conta. Por favor, cambie o contrasinal inmediatamente ou contacte coa administración da instancia si non ten acceso a súa conta.
         subject: 'Mastodon: email cambiado'
+        title: Novo enderezo de correo
       password_change:
+        explanation: Cambiouse o contrasinal da súa conta.
+        extra: Si non cambio o contrasinal, é probable que alguén obtivese acceso a súa conta. Por favor cambie o contrasinal inmediatamente ou contacte coa administración da instancia si non ten acceso a súa conta.
         subject: 'Mastodon: contrasinal cambiado'
+        title: Contrainal cambiado
       reconfirmation_instructions:
+        explanation: Confirme o novo enderezo para cambiar o correo-e.
+        extra: Si vostede non fixo esta petición, ignore este correo por favor. Este enderezo de correo-e para a conta Mastodon non cambiará ate que acceda a ligazón superior.
         subject: 'Mastodon: Confirme email para %{instance}'
+        title: Verificación do enderezo de correo-e
       reset_password_instructions:
+        action: Cambiar contrasinal
+        explanation: Solicitou un novo contrasinal para a súa conta.
+        extra: Si non fixo esta solicitude, por favor ignore este correo. O seu contrasinal non cambiará ate que acceda a ligazón superior e cree unha nova.
         subject: 'Mastodon: Instruccións para restablecer o contrasinal'
+        title: Restablecer contrasinal
       unlock_instructions:
         subject: 'Mastodon: Instruccións para desbloquear'
     omniauth_callbacks:
diff --git a/config/locales/devise.hr.yml b/config/locales/devise.hr.yml
index 40e0effcf..d578e404f 100644
--- a/config/locales/devise.hr.yml
+++ b/config/locales/devise.hr.yml
@@ -17,6 +17,9 @@ hr:
     mailer:
       confirmation_instructions:
         subject: 'Mastodon: Upute za potvrđivanje'
+      email_changed:
+        subject: 'Mastodon: Email adresa je promijenjena'
+        title: Nova email adresa
       password_change:
         subject: 'Mastodon: Lozinka je promijenjena'
       reset_password_instructions:
diff --git a/config/locales/devise.hu.yml b/config/locales/devise.hu.yml
index 911ba7b94..79ee3b194 100644
--- a/config/locales/devise.hu.yml
+++ b/config/locales/devise.hu.yml
@@ -6,7 +6,7 @@ hu:
       send_instructions: Pár percen belül kapni fogsz egy e-mailt az e-mail címed megerősítéséhez szükséges lépésekről.
       send_paranoid_instructions: Ha az e-mail címed létezik az adatbázisunkban, pár percen belül kapni fogsz egy e-mailt az e-mail címed megerősítéséhez szükséges lépésekről.
     failure:
-      already_authenticated: Már bejelentkeztél
+      already_authenticated: Már bejelentkeztél.
       inactive: Fiókod még nem lett aktiválva.
       invalid: Helytelen %{authentication_keys} vagy jelszó.
       last_attempt: Már csak egy próbálkozásod maradt mielőtt a fiókod lezárásra kerül.
@@ -17,11 +17,32 @@ hu:
       unconfirmed: A folytatás előtt meg kell erősítened az e-mail címed.
     mailer:
       confirmation_instructions:
+        action: Erősítsd meg az e-mail címedet
+        explanation: Ezzel az e-mail címmel kezdeményeztek regisztrációt a(z) %{host} oldalon. Csak egy kattintás, és a felhasználói fiókdat aktiváljuk. Ha a regisztrációt nem te kezdeményezted, kérjük tekintsd ezt az e-mailt tárgytalannak.
+        extra_html: Kérjük tekintsd át a <a href="%{terms_path}">az instancia szabályzatát</a> és <a href="%{policy_path}">a felhasználási feltételeket</a>.
         subject: 'Mastodon: Megerősítési lépések'
+        title: E-mail cím megerősítése
+      email_changed:
+        explanation: 'A fiókodhoz tartozó e-mail címet az alábbira módosítod:'
+        extra: Ha nem te kezdeményezted a fiókodhoz tartozó e-mail cím módosítását, valaki hozzáférhetett a fiókodhoz. Legjobb, ha azonnal megváltoztatod a jelszavadat; ha nem férsz hozzá a fiókodhoz, vedd fel a kapcsolatot az instanciád adminisztrátorával.
+        subject: 'Mastodon: a fiókodhoz tartozó e-mail címet megváltoztattuk'
+        title: Új e-mail cím
       password_change:
+        explanation: A fiókodhoz tartozó jelszót megváltoztattuk.
+        extra: Ha nem te kezdeményezted a fiókodhoz tartozó jelszó módosítását, valaki hozzáférhetett a fiókodhoz. Legjobb, ha azonnal megváltoztatod a jelszavadat; ha nem férsz hozzá a fiókodhoz, vedd fel a kapcsolatot az instanciád adminisztrátorával.
         subject: 'Mastodon: Jelszó megváltoztatva'
+        title: Sikeres jelszó-módosítás
+      reconfirmation_instructions:
+        explanation: Az e-mail cím megváltoztatásához meg kell erősítened az új címet.
+        extra: Amennyiben nem te kezdeményezted a módosítást, kérjük tekintsd ezt az e-mailt tárgytalannak. A Mastodon fiókodhoz tartozó e-mail címed változatlan marad mindaddig, amíg rá nem kattintasz a fenti linkre.
+        subject: 'Mastodon: erősítsd meg a(z) %{instance} instanciához tartozó e-mail címed'
+        title: E-mail cím megerősítése
       reset_password_instructions:
+        action: Jelszó módosítása
+        explanation: A fiókodhoz tartozó jelszó módosítását kezdeményezted.
+        extra: Amennyiben nem te kezdeményezted a módosítást, kérjük tekintsd ezt az e-mailt tárgytalannak. A Mastodon fiókodhoz tartozó jelszavad változatlan marad mindaddig, amíg újat nem hozol létre a fenti linkre kattintva.
         subject: 'Mastodon: Jelszó visszaállítási lépések'
+        title: Jelszó visszaállítása
       unlock_instructions:
         subject: 'Mastodon: Feloldási lépések'
     omniauth_callbacks:
diff --git a/config/locales/devise.nl.yml b/config/locales/devise.nl.yml
index 57e85a7b5..ce87c393f 100644
--- a/config/locales/devise.nl.yml
+++ b/config/locales/devise.nl.yml
@@ -18,15 +18,32 @@ nl:
       unconfirmed: Je dient eerst jouw account te bevestigen.
     mailer:
       confirmation_instructions:
+        action: E-mailadres verifiëren
+        explanation: Je hebt een account op %{host} aangemaakt en met één klik kun je deze activeren. Wanneer jij dit account niet hebt aangemaakt, mag je deze e-mail negeren.
+        extra_html: Bekijk ook de <a href="%{terms_path}">regels van de Mastodonserver</a> en <a href="%{policy_path}">onze gebruikersvoorwaarden</a>.
         subject: 'Mastodon: E-mail bevestigen voor %{instance}'
+        title: E-mailadres verifiëren
       email_changed:
+        explanation: 'Het e-mailadres van jouw account is gewijzigd naar:'
+        extra: Wanneer jij jouw e-mailadres niet hebt gewijzigd, heeft iemand zich waarschijnlijk toegang tot jouw account verschaft. Verander onmiddellijk jouw wachtwoord of neem contact op met de beheerder van jouw Mastodonserver wanneer je niet meer kunt inloggen.
         subject: 'Mastodon: E-mailadres is veranderd'
+        title: Nieuw e-mailadres
       password_change:
+        explanation: Het wachtwoord van dit account is gewijzigd.
+        extra: Wanneer jij jouw wachtwoord niet hebt gewijzigd, heeft iemand zich waarschijnlijk toegang tot jouw account verschaft. Verander onmiddellijk jouw wachtwoord of neem contact op met de beheerder van jouw Mastodonserver wanneer je niet meer kunt inloggen.
         subject: 'Mastodon: Wachtwoord veranderd'
+        title: Wachtwoord gewijzigd
       reconfirmation_instructions:
+        explanation: Bevestig jouw nieuw e-mailadres om deze te wijzigen.
+        extra: Wanneer jij deze wijziging niet hebt uitgevoerd, mag je deze e-mail negeren. Het e-mailadres van jouw Mastodonaccount wordt pas daadwerkelijk gewijzigd totdat je de link hierboven aanklikt.
         subject: 'Mastodon: Bevestig het e-mailadres voor %{instance}'
+        title: E-mailadres verifiëren
       reset_password_instructions:
+        action: Wachtwoord wijzigen
+        explanation: Jij hebt een nieuw wachtwoord voor jouw account aangevraagd.
+        extra: Wanneer jij dit niet hebt aangevraagd, mag je deze e-mail negeren. Jouw wachtwoord wordt pas gewijzigd nadat je de link hierboven hebt aangeklikt en een nieuw wachtwoord aanmaakt.
         subject: 'Mastodon: Wachtwoord opnieuw instellen'
+        title: Wachtwoord opnieuw instellen
       unlock_instructions:
         subject: 'Mastodon: Instructies om opschorten account ongedaan te maken'
     omniauth_callbacks:
diff --git a/config/locales/devise.no.yml b/config/locales/devise.no.yml
index 5d3e71495..ca16c6ba5 100644
--- a/config/locales/devise.no.yml
+++ b/config/locales/devise.no.yml
@@ -17,11 +17,32 @@
       unconfirmed: Du må bekrefte e-postadressen din før du kan fortsette.
     mailer:
       confirmation_instructions:
+        action: Bekreft e-postadresse
+        explanation: Du har laget en konto på %{host} med denne e-postadressen. Du er ett klikk unna å aktivere den. Hvis dette ikke var deg, vennligst se bort fra denne e-posten.
+        extra_html: Vennligst også sjekk ut <a href="%{terms_path}">instansens regler </a> og <a href="%{policy_path}">våre bruksvilkår</a>.
         subject: 'Mastodon: Instruksjoner for å bekrefte e-postadresse'
+        title: Bekreft e-postadresse
+      email_changed:
+        explanation: 'E-postadressen til din konto endres til:'
+        extra: Hvis du ikke endret din e-postadresse, er det sannsynlig at noen har fått tilgang til din konto. Vennligst endre ditt passord umiddelbart eller kontakt instansens administrator dersom du er utestengt fra kontoen din.
+        subject: 'Mastadon: E-postadresse endret'
+        title: Ny e-postadresse
       password_change:
+        explanation: Passordet til din konto har blitt endret.
+        extra: Hvis du ikke endret ditt passord, er det sannsynlig at noen har fått tilgang til din konto. Vennligst endre ditt passord umiddelbart eller kontakt instansens administrator dersom du er utestengt fra kontoen din.
         subject: 'Mastodon: Passord endret'
+        title: Passord endret
+      reconfirmation_instructions:
+        explanation: Din nye e-postadresse må bekreftes for å bli endret.
+        extra: Se bort fra denne e-posten dersom du ikke gjorde denne endringen. E-postadressen for Mastadon-kontoen blir ikke endret før du trykker på lenken over.
+        subject: 'Mastodon: Bekreft e-postadresse for %{instance}'
+        title: Bekreft e-postadresse
       reset_password_instructions:
+        action: Endre passord
+        explanation: Du ba om et nytt passord for din konto.
+        extra: Se bort fra denne e-posten dersom du ikke ba om dette. Ditt passord blir ikke endret før du trykker på lenken over og lager et nytt.
         subject: 'Mastodon: Hvordan nullstille passord'
+        title: Nullstill passord
       unlock_instructions:
         subject: 'Mastodon: Instruksjoner for å gjenåpne konto'
     omniauth_callbacks:
diff --git a/config/locales/devise.oc.yml b/config/locales/devise.oc.yml
index de87ac1e2..06617af34 100644
--- a/config/locales/devise.oc.yml
+++ b/config/locales/devise.oc.yml
@@ -12,33 +12,50 @@ oc:
       last_attempt: Vos demòra un ensag abans que vòstre compte siasque blocat.
       locked: Vòstre compte es blocat.
       not_found_in_database: Corrièl o senhal invalid.
-      timeout: Vòstra session s’a acabat. Mercés de vos tornar connectar per contunhar.
+      timeout: Vòstra session a expirat. Mercés de vos tornar connectar per contunhar.
       unauthenticated: Vos cal vos connectar o marcar abans de contunhar.
       unconfirmed: Vos cal confirmar vòstra adreça de corrièl abans de contunhar.
     mailer:
       confirmation_instructions:
-        subject: Mercés de confirmar vòstra inscripcion sus %{instance}
+        action: Verificar l’adreça de corrièl
+        explanation: Venètz de crear un compte sus %{host} amb aquesta adreça de corrièl. Vos manca pas qu’un clic per l’activar. S’èra pas vosautre mercés de far pas cas a aqueste messatge.
+        extra_html: Pensatz tanben de gaitar <a href="%{terms_path}">las règlas de l’instància</a> e <a href="%{policy_path}">nòstres tèrmes e condicions d’utilizacion</a>.
+        subject: 'Mastodon : consignas de confirmacion per %{instance}'
+        title: Verificatz l’adreça de corrièl
       email_changed:
+        explanation: 'L’adreça per aqueste compte es ara :'
+        extra: S’avètz pas demandat aqueste cambiament d’adreça, poiriá arribar que qualqu’un mai aguèsse agut accès a vòstre compte. Mercés de cambiar sulpic vòstre senhal o de contactar vòstre administrator d’instància se l’accès a vòstre compte vos es barrat.
         subject: 'Mastodon : corrièl cambiat'
+        title: Nòva adreça de corrièl
       password_change:
+        explanation: Lo senhal per vòstre compte a cambiat.
+        extra: S’avètz pas demandat aqueste cambiament de senhal, poiriá arribar que qualqu’un mai aguèsse agut accès a vòstre compte. Mercés de cambiar sulpic vòstre senhal o de contactar vòstre administrator d’instància se l’accès a vòstre compte vos es barrat.
         subject: Mastodon : senhal cambiat
+        title: Senhal cambiat
       reconfirmation_instructions:
-        subject: 'Mastodon : Confirmatz l’adreça per %{instance}'
+        explanation: Confirmar la nòva adreça per cambiar lo corrièl.
+        extra: S’avètz pas res demandat, fasquètz pas cas a aqueste corrièl. Vòstre corrièl per Mastodon cambiarà pas se clicatz pas lo ligam dessús.
+        subject: 'Mastodon : confirmatz l’adreça per %{instance}'
+        title: Verificatz l’adreça de corrièl
       reset_password_instructions:
-        subject: Mastodon : instruccions per reïnicializar lo senhal
+        action: Cambiament de senhal
+        explanation: Avètz demandat un nòu senhal per vòstre compte.
+        extra: S’avètz pas res demandat, fasquètz pas cas a aqueste corrièl. Vòstre senhal cambiarà pas se clicatz pas lo ligam e que ne causissètz pas un novèl.
+        subject: Mastodon : consignas per reïnicializar lo senhal
+        title: Reïnicializacion del senhal
       unlock_instructions:
-        subject: Mastodon : instuccions de desblocatge
+        subject: Mastodon : consignas de desblocatge
     omniauth_callbacks:
       failure: Fracàs al moment de vos autentificar de %{kind} perque "%{reason}".
       success: Sètz ben autentificat dempuèi lo compte %{kind}.
     passwords:
       no_token: Podètz pas accedir a aquesta pagina sens venir d’un corriel de reïnicializacion de senhal. S’es lo cas, mercés de verificar qu’avètz ben utilizat l’URL donada de manièra complèta.
-      send_instructions: Recebretz un corrièl amb las instruccions per reĩnicializar vòstre senhal dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
+      send_instructions: Recebretz un corrièl amb las consignas per reĩnicializar vòstre senhal dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
       send_paranoid_instructions: Se vòstra adreça de corrièl existís dins nòstra basa de donadas, recebretz un ligam per reĩnicializar vòstre senhal dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
       updated: Vòstre senhal es ben estat cambiat. Sètz ara connectat.
       updated_not_active: Vòstre senhal es ben estat cambiat.
     registrations:
-      destroyed: Adiu ! Vòstra inscripcion es estada anullada amb succès. Esperem vos tornar veire lèu.
+      destroyed: Adieu-siatz ! Vòstra inscripcion es estada anullada amb succès. Esperem vos tornar veire lèu.
       signed_up: La benvenguda ! Sètz ben marcat al malhum.
       signed_up_but_inactive: Sètz ben marcat. Pasmens, avèm pas pogut vos connectar perque vòstre compte es pas encara validat.
       signed_up_but_locked: Sètz ben marcat. Pasmens, avèm pas pogut vos connectar perque vòstre compte es pas encara blocat.
@@ -50,8 +67,8 @@ oc:
       signed_in: Connectat amb succès.
       signed_out: Desconnectat amb succès.
     unlocks:
-      send_instructions: Recebretz un corrièl amb las instruccions per o desblocar dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
-      send_paranoid_instructions: Se vòstre compte existís recebretz un corrièl amb las instruccions per o desblocar dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
+      send_instructions: Recebretz un corrièl amb las consignas per o desblocar dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
+      send_paranoid_instructions: Se vòstre compte existís recebretz un corrièl amb las consignas per o desblocar dins una estona. Mercés de verificar tanben vòstre dorsièr de corrièls indesirables.
       unlocked: Vòstre compte es estat desblocat amb succès. Mercés de vos connectar per contunhar.
   errors:
     messages:
diff --git a/config/locales/devise.pt-BR.yml b/config/locales/devise.pt-BR.yml
index eab86c802..5f47bc901 100644
--- a/config/locales/devise.pt-BR.yml
+++ b/config/locales/devise.pt-BR.yml
@@ -13,19 +13,36 @@ pt-BR:
       locked: A sua conta está bloqueada.
       not_found_in_database: "%{authentication_keys} ou senha inválida."
       timeout: A sua sessão expirou. Por favor, entre novamente para continuar.
-      unauthenticated: Você precisa entrar ou registar-se antes de continuar.
+      unauthenticated: Você precisa entrar ou cadastrar-se antes de continuar.
       unconfirmed: Você precisa confirmar o seu endereço de e-mail antes de continuar.
     mailer:
       confirmation_instructions:
+        action: Verificar endereço de e-mail
+        explanation: Você criou uma conta em %{host} com esse endereço de e-mail. Você está a um clique de ativá-la. Se não foi você, por favor ignore esse e-mail.
+        extra_html: Por favor confira também <a href="%{terms_path}">as regras da instância</a> e <a href="%{policy_path}">nossos termos de serviço</a>.
         subject: 'Mastodon: Instruções de confirmação'
+        title: Verifique o endereço de e-mail
       email_changed:
+        explanation: 'O e-mail associado à sua conta será mudado para:'
+        extra: Se você não mudou seu e-mail é possível que alguém tenha conseguido acesso à sua conta. Por favor mude sua senha imediatamente ou entre em contato com um administrador da sua instância se você ficou sem acesso à sua conta.
         subject: 'Mastodon: Email alterado'
+        title: Novo endereço de e-mail
       password_change:
+        explanation: A senha da sua conta foi mudada.
+        extra: Se você não mudou a sua senha, é possível que alguém tenha conseguido acesso à sua conta. Por favor mude sua senha imediatamente ou entre em contato com um administrador da sua instância se você ficou sem acesso à sua conta.
         subject: 'Mastodon: Senha modificada'
+        title: Senha alterada
       reconfirmation_instructions:
+        explanation: Confirme o seu novo endereço para mudar seu e-mail.
+        extra: Se essa mudança não foi iniciada por você, por favor ignore esse e-mail. O endereço de e-mail para essa conta do Mastodon não irá mudar até que você acesse o link acima.
         subject: 'Mastodon: Confirmar emai para %{instance}'
+        title: Verificar o endereço de e-mail
       reset_password_instructions:
+        action: Mudar a senha
+        explanation: Você pediu uma nova senha para sua conta.
+        extra: Se você não fez esse pedido, por favor ignore esse e-mail. Sua senha não irá mudar até que você acesse o link acima e crie uma nova.
         subject: 'Mastodon: Instruções para mudança de senha'
+        title: Redefinir a senha
       unlock_instructions:
         subject: 'Mastodon: Instruções de desbloqueio'
     omniauth_callbacks:
diff --git a/config/locales/devise.pt.yml b/config/locales/devise.pt.yml
index a09443a9a..5fd54ff50 100644
--- a/config/locales/devise.pt.yml
+++ b/config/locales/devise.pt.yml
@@ -17,11 +17,32 @@ pt:
       unconfirmed: Tens de confirmar o teu endereço de email antes de continuar.
     mailer:
       confirmation_instructions:
+        action: Verificar o endereço de e-mail
+        explanation: Criaste uma conta em %{host} com este endereço de e-mail. Estás a um clique de activá-la. Se não foste tu que fizeste este registo, por favor ignora esta mensagem.
+        extra_html: Por favor vê as <a href="%{terms_path}">as regras da instância</a> e os <a href="%{policy_path}">termos de serviço</a>.
         subject: 'Mastodon: Instruções de confirmação %{instance}'
+        title: Verificar o endereço de e-mail
+      email_changed:
+        explanation: 'O e-mail associado à tua conta será alterado para:'
+        extra: Se não alteraste o teu e-mail é possível que alguém tenha conseguido aceder à tua conta. Por favor muda a tua palavra-passe imediatamente ou entra em contato com um administrador da tua instância se ficaste sem acesso à tua conta.
+        subject: 'Mastodon: Email alterado'
+        title: Novo endereço de e-mail
       password_change:
+        explanation: A palavra-passe da tua conta foi alterada.
+        extra: Se não alteraste a tua palavra-passe, é possível que alguém tenha conseguido aceder à tua conta. Por favor muda a tua palavra-passe imediatamente ou entra em contato com um administrador da tua instância se ficaste sem acesso à tua conta.
         subject: 'Mastodon: Nova palavra-passe'
+        title: Palavra-passe alterada
+      reconfirmation_instructions:
+        explanation: Confirma o teu novo endereço para alterar o e-mail.
+        extra: Se esta mudança não foi iniciada por ti, por favor ignora este e-mail. O endereço de e-mail para a tua conta do Mastodon não irá mudar enquanto não acederes ao link acima.
+        subject: 'Mastodon: Confirmação de e-mail %{instance}'
+        title: Validar o endereço de e-mail
       reset_password_instructions:
-        subject: 'Mastodon: Instruções para editar a palavra-passe'
+        action: Alterar palavra-passe
+        explanation: Pediste a alteração da palavra-passe da tua conta.
+        extra: Se não fizeste este pedido, por favor ignora este e-mail. A tua palavra-passe não irá mudar se não acederes ao link acima e criares uma nova.
+        subject: 'Mastodon: Instruções para alterar a palavra-passe'
+        title: Solicitar nova palavra-passe
       unlock_instructions:
         subject: 'Mastodon: Instruções para desbloquear a tua conta'
     omniauth_callbacks:
diff --git a/config/locales/devise.ru.yml b/config/locales/devise.ru.yml
index b5b0321bd..f80f7ad05 100644
--- a/config/locales/devise.ru.yml
+++ b/config/locales/devise.ru.yml
@@ -17,11 +17,32 @@ ru:
       unconfirmed: Вам необходимо подтвердить ваш адрес e-mail для продолжения.
     mailer:
       confirmation_instructions:
-        subject: 'Mastodon: Инструкция по подтверждению'
+        action: Подтвердите e-mail адрес
+        explanation: Вы создали учётную запись на сайте %{host}, используя этот e-mail адрес. Остался лишь один шаг для активации. Если это были не вы, просто игнорируйте письмо.
+        extra_html: Пожалуйста, ознакомьтесь <a href="%{terms_path}">правилами узла</a> and <a href="%{policy_path}">условиями пользования Сервисом</a>.
+        subject: 'Mastodon: Инструкция по подтверждению на узле %{instance}'
+        title: Подтвердите e-mail адрес
+      email_changed:
+        explanation: 'E-mail адрес Вашей учётной записи будет изменён на:'
+        extra: Если Вы не меняли адрес e-mail, возможно кто-то получил доступ к Вашей учётной записи. Пожалуйста, срочно смените пароль или свяжитесь с администратором узла, если у Вас нет доступа к учётной записи
+        subject: 'Mastodon: Адрес e-mail изменён'
+        title: Новый адрес e-mail
       password_change:
+        explanation: Пароль Вашей учётной записи был изменён.
+        extra: Если Вы не меняли пароль, возможно кто-то получил доступ к Вашей учётной записи. Пожалуйста, срочно смените пароль или свяжитесь с администратором узла, если у Вас нет доступа к учётной записи.
         subject: 'Mastodon: Пароль изменен'
+        title: Пароль изменён
+      reconfirmation_instructions:
+        explanation: Подтвердите новый адрес для смены e-mail.
+        extra: Если смену e-mail инициировали не Вы, пожалуйста, игнорируйте это письмо. Адрес e-mail для учётной записи Mastodon не будет изменён, пока Вы не перейдёте по ссылке выше.
+        subject: 'Mastodon: Подтверждение e-mail для узла %{instance}'
+        title: Подтвердите e-mail адрес
       reset_password_instructions:
-        subject: 'Mastodon: Инструкция по сбросу пароля'
+        action: Смена пароля
+        explanation: Вы запросили новый пароль для Вашей учётной записи.
+        extra: Если это сделали не Вы, пожалуйста, игнорируйте письмо. Ваш пароль не будет изменён, пока Вы не перейдёте по ссылке выше и не создадите новый пароль.
+        subject: 'Mastodon: инструкция по смене пароля'
+        title: Сброс пароля
       unlock_instructions:
         subject: 'Mastodon: Инструкция по разблокировке'
     omniauth_callbacks:
diff --git a/config/locales/devise.sk.yml b/config/locales/devise.sk.yml
index a4b92886d..2ce328d22 100644
--- a/config/locales/devise.sk.yml
+++ b/config/locales/devise.sk.yml
@@ -6,38 +6,59 @@ sk:
       send_instructions: O niekoľko minút obdržíte email s inštrukciami ako potvrdiť váš účet.
       send_paranoid_instructions: Ak sa váš email nachádza v našej databáze, obdržíte email s inštrukciami ako potvrdiť váš účet.
     failure:
-      already_authenticated: Už ste prihlásený.
+      already_authenticated: Už ste prihlásený/á.
       inactive: Váš účet ešte nebol aktivovaný.
       invalid: Nesprávny %{authentication_keys} alebo heslo.
       last_attempt: Máte posledný pokus pred zamknutím vašeho účtu.
       locked: Váš účet je zamknutý.
       not_found_in_database: Nesprávny %{authentication_keys} alebo heslo.
-      timeout: Vaša session vypršala. Na pokračovanie sa prosím znovu prihláste.
+      timeout: Vaša aktívna sezóna vypršala. Pre pokračovanie sa prosím znovu prihláste.
       unauthenticated: Pred pokračovaním sa musíte zaregistrovať alebo prihlásiť.
       unconfirmed: Pred pokračovaním musíte potvrdiť svoj email.
     mailer:
       confirmation_instructions:
+        action: Potvrite emailovú adresu
+        explanation: S touto email adresou ste si vytvoril/a účet na %{host}. Si iba jeden klik od jeho aktivácie. Pokiaľ ste to ale nebol/a vy, prosím ignoruj tento email.
+        extra_html: Prosím pozri sa aj na <a href="%{terms_path}"> pravidla tohto servera,</a> a <a href="%{policy_path}"> naše užívaťeľské podiemky</a>.
         subject: 'Mastodon: Potvrdzovacie inštrukcie pre %{instance}'
+        title: Potvrď emailovú adresu
+      email_changed:
+        explanation: 'Emailová adresa tvojho účtu bude zmenená na:'
+        extra: Pokiaľ si nezmenil/a svoj email, je pravdepodobné že niekto iný získal prístup k tvojmu účtu. Naliehavo preto prosím zmeň svoje heslo, alebo kontaktuj administrátora tohto serveru pokiaľ si vymknutý/á zo svojho účtu.
+        subject: 'Mastodon: Emailová adresa bola zmenená'
+        title: Nová emailová adresa
       password_change:
+        explanation: Heslo k vašemu účtu bolo zmenené.
+        extra: Pokiaľ si nezmenil/a svoje heslo, je pravdepodobné že niekto iný získal prístup k tvojmu účtu. Naliehavo preto prosím zmeň svoje heslo, alebo kontaktuj administrátora tohto serveru pokiaľ si vymknutý/á zo svojho účtu.
         subject: 'Mastodon: Heslo bolo zmenené'
+        title: Heslo bolo zmenené
+      reconfirmation_instructions:
+        explanation: Potvrď novú emailovú adresu na ktorú chceš zmeniť svoj email.
+        extra: Pokiaľ si túto akciu nevyžiadal/a, prosím ignoruj tento email. Emailová adresa pre tvoj Mastodon účet totiž nebude zmenená pokiaľ nepostúpiš na adresu uvedenú vyššie.
+        subject: 'Mastodon: Potvrďenie emailu pre %{instance}'
+        title: Overiť emailovú adresu
       reset_password_instructions:
+        action: Zmeniť heslo
+        explanation: Vyžiadal/a ste si nové heslo pre svoj účet.
+        extra: Pokiaľ si túto akciu nevyžiadal/a, prosím ignoruj tento email. Tvoje heslo nebude zmenené pokiaľ nepostúpiš na adresu uvedenú vyššie a vytvoríš si nové.
         subject: 'Mastodon: Inštrukcie pre obnovu hesla'
+        title: Nastav nové heslo
       unlock_instructions:
         subject: 'Mastodon: Inštrukcie pre odomknutie účtu'
     omniauth_callbacks:
-      failure: Nebolo možné vás autentifikovať z %{kind} z dôvodu "%{reason}".
-      success: Úspešne autentifikovaný z účtu %{kind}.
+      failure: Nebolo možné ťa overiť z dôvodu,%{kind} že "%{reason}".
+      success: Úspešné overenie z účtu %{kind}.
     passwords:
       no_token: Túto stránku nemôžete navštíviť pokiaľ neprichádzate z emailu s inštrukciami na obnovu hesla. Pokiaľ prichádzate z tohto emailu, prosím uistite sa že ste použili celú URL z emailu.
-      send_instructions: Ak zadaný email existuje v našej databáze tak o niekoľko minút obdržíte email s inštrukciami ako nastaviť nové heslo.
-      send_paranoid_instructions: Ak zadaný email existuje v našej databáze, obdržíte odkaz na obnovu hesla na svoj email.
-      updated: Vaše heslo bolo úspešne zmenené. Teraz ste prihlásený.
+      send_instructions: Ak zadaný email existuje v našej databázi, tak o niekoľko minút obdržíte email s inštrukciami ako nastaviť nové heslo.
+      send_paranoid_instructions: Ak zadaný email existuje v našej databázi, zachvíľu obdržíte odkaz na obnovu hesla na svoj email. Skontrolujte aj spam ak tento email nevidíte.
+      updated: Vaše heslo bolo úspešne zmenené. Teraz ste prihlásený/á.
       updated_not_active: Vaše heslo bolo úspešne zmenené.
     registrations:
-      destroyed: Dovidenia! Váš účet bol úspešne zrušený. Dúfame že sa opäť niekedy zastavíte.
+      destroyed: Dovidenia! Váš účet bol úspešne zrušený. Dúfame ale, že sa tu opäť niekedy zastavíte.
       signed_up: Vitajte! Vaša registrácia bola úspešná.
-      signed_up_but_inactive: Registrácia bola úspešná. Avšak, účet ešte nebol aktivovaný, takže vás nemôžeme prihlásiť.
-      signed_up_but_locked: Prihlasovanie úspešné. Avšak, účet je zablokovaný, takže vás nemôžeme prihlásiť.
+      signed_up_but_inactive: Registrácia bola úspešná. Avšak, účet ešte nebol aktivovaný, takže ťa nemôžeme prihlásiť.
+      signed_up_but_locked: Prihlasovanie úspešné. Avšak tvoj účet je zamknutý, takže ťa nieje možné prihlásiť.
       signed_up_but_unconfirmed: Správa s odkazom potvrdzujúcim registráciu bola poslaná na váš email. Pre aktváciu účtu, kliknite na daný odkaz.
       update_needs_confirmation: Účet bol úspešne zmenený ale ešte potrebujeme overiť vašu novú emailovú adresu. Pre overenie prosím kliknite na link v správe ktorú sme vám poslali na email.
       updated: Váš účet bol úspešne aktualizovaný.
diff --git a/config/locales/devise.sv.yml b/config/locales/devise.sv.yml
index 3ac0c6d10..456e38581 100644
--- a/config/locales/devise.sv.yml
+++ b/config/locales/devise.sv.yml
@@ -17,11 +17,32 @@ sv:
       unconfirmed: Du måste bekräfta din e-postadress innan du fortsätter.
     mailer:
       confirmation_instructions:
+        action: Verifiera e-postadress
+        explanation: Du har skapat ett konto på %{host} med den här e-postadressen. Du är ett klick bort från att aktivera det. Om det inte var du ignorerar det här e-postmeddelandet.
+        extra_html: Kolla gärna också <a href="%{terms_path}">instansens regler</a> och <a href="%{policy_path}">våra användarvillkor</a>.
         subject: 'Mastodon: Bekräftelsesinstruktioner för %{instance}'
+        title: Verifiera e-postadress
+      email_changed:
+        explanation: 'E-postadressen för ditt konto ändras till:'
+        extra: Om du inte ändrade din e-post är det troligt att någon har fått tillgång till ditt konto. Vänligen ändra ditt lösenord omedelbart eller kontakta instansadministratören om du är låst ur ditt konto.
+        subject: 'Mastodon: E-post ändrad'
+        title: Ny e-postadress
       password_change:
+        explanation: Lösenordet för ditt konto har ändrats.
+        extra: Om du inte ändrade ditt lösenord är det troligt att någon har fått tillgång till ditt konto. Vänligen ändra ditt lösenord omedelbart eller kontakta instansadministratören om du är utelåst från ditt konto.
         subject: 'Mastodon: Lösenord ändrat'
+        title: lösenordet ändrat
+      reconfirmation_instructions:
+        explanation: Bekräfta den nya adressen för att ändra din e-postadress.
+        extra: Om den här ändringen inte initierades av dig kan du ignorerar det här e-postmeddelandet. E-postadressen för Mastodon-kontot ändras inte förrän du kommer åt länken ovan.
+        subject: 'Mastodon: Bekräfta e-post för %{instance}'
+        title: Verifiera e-postadressen
       reset_password_instructions:
+        action: Ändra lösenord
+        explanation: Du begärde ett nytt lösenord för ditt konto.
+        extra: Om du inte begärt detta kan du ignorerar det här e-postmeddelandet. Ditt lösenord ändras inte förrän du öppnar länken ovan och skapar ett nytt.
         subject: 'Mastodon: Instruktioner för återställning av lösenord'
+        title: Lösenordsåterställning
       unlock_instructions:
         subject: 'Mastodon: Lås upp instruktioner'
     omniauth_callbacks:
@@ -31,7 +52,7 @@ sv:
       no_token: Du kan inte komma åt den här sidan utan att komma från ett e-postmeddelande för lösenordsåterställning. Om du kommer från ett lösenordsåterställt e-postmeddelande, var vänlig och se till att du använde hela webbadressen.
       send_instructions: Om din e-postadress finns i vår databas, får du en länk för återställning av lösenord på din e-postadress om några minuter. Kontrollera din spammapp om du inte fick det här e-postmeddelandet.
       send_paranoid_instructions: Om din e-postadress finns i vår databas, får du en länk för återställning av lösenord på din e-postadress om några minuter. Kontrollera din spammapp om du inte fick det här e-postmeddelandet.
-      updated: Your password has been changed successfully. You are now signed in.
+      updated: Ditt lösenord har ändrats framgångsrikt. Du är nu inloggad.
       updated_not_active: Ditt lösenord har ändrats. Du är nu inloggad.
     registrations:
       destroyed: Adjö! Ditt konto har blivit nerstängt. Vi hoppas att vi ses snart igen.
diff --git a/config/locales/devise.tr.yml b/config/locales/devise.tr.yml
index ade23769d..fb819978f 100644
--- a/config/locales/devise.tr.yml
+++ b/config/locales/devise.tr.yml
@@ -3,7 +3,7 @@ tr:
   simple_form:
     hints:
       defaults:
-        avatar: En fazla 2MB olacak şekilde PNG, GIF veya JPG formatında yükleyiniz. 120x120px büyüklüğüne indirgenecektir
+        avatar: En fazla 2MB olacak şekilde PNG, GIF veya JPG formatında yükleyiniz. 400x400px büyüklüğüne indirgenecektir
         display_name: "%{count} karakter kaldı"
         header: En fazla 2MB olacak şekilde PNG, GIF veya JPG formatında yükleyiniz. 700x335px büyüklüğüne indirgenecektir.
         locked: Takipçilerinizi manuel olarak kabul etmenizi ve gönderilerinizi varsayılan olarak sadece takipçilerinizin göreceği şekilde paylaşmanızı sağlar.
diff --git a/config/locales/doorkeeper.ar.yml b/config/locales/doorkeeper.ar.yml
index 107677837..d13c22386 100644
--- a/config/locales/doorkeeper.ar.yml
+++ b/config/locales/doorkeeper.ar.yml
@@ -4,7 +4,7 @@ ar:
     attributes:
       doorkeeper/application:
         name: التسمية
-        redirect_uri: Redirect URI
+        redirect_uri: عنوان التحويل
         scopes: المجالات
         website: تطبيق الويب
     errors:
@@ -33,7 +33,7 @@ ar:
       help:
         native_redirect_uri: إستخدم %{native_redirect_uri} للاختبار و التجريب محليا
         redirect_uri: إستخدم خطا واحدا لكل رابط
-        scopes: Separate scopes with spaces. Leave blank to use the default scopes.
+        scopes: تقسيم المجالات بمسافات بيضاء. بدون تحديد قيمة يعني ذلك استخدام المجالات الإفتراضية.
       index:
         application: تطبيق
         callback_url: رابط رد النداء
@@ -77,20 +77,14 @@ ar:
         title: تطبيقاتك المرخص لها
     errors:
       messages:
-        access_denied: The resource owner or authorization server denied the request.
-        credential_flow_not_configured: Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.
-        invalid_client: Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.
-        invalid_grant: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
-        invalid_redirect_uri: The redirect uri included is not valid.
-        invalid_request: The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.
-        invalid_resource_owner: The provided resource owner credentials are not valid, or resource owner cannot be found
-        invalid_scope: The requested scope is invalid, unknown, or malformed.
+        access_denied: لقد رفض مالك المَورِدِ أو تصريح السيرفر طلبك.
+        invalid_client: فشلت المصادقة مع العميل لأنه العميل مجهول أو لغياب المصادقة ضمن العميل أو أنّ أسلوب المصادقة غير مدعومة.
+        invalid_redirect_uri: إنّ عنوان إعادة التحويل غير صالح.
+        invalid_scope: المجال المطلوب غير صحيح أو مجهول أو مُعبَّر عنه بشكل خاطئ.
         invalid_token:
           expired: إنتهت فترة صلاحيته رمز المصادقة
           revoked: تم إبطال رمز المصادقة
           unknown: رمز المصادقة غير صالح
-        resource_owner_authenticator_not_configured: Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.
-        server_error: The authorization server encountered an unexpected condition which prevented it from fulfilling the request.
         temporarily_unavailable: تعذر على خادم التفويض معالجة الطلب و ذلك بسبب زيادة مؤقتة في التحميل أو عملية صيانة مبرمجة على الخادم.
         unauthorized_client: لا يصرح للعميل بتنفيذ هذا الطلب باستخدام هذه الطريقة.
         unsupported_grant_type: هذا النوع من منح التصريح غير معتمد في خادم الترخيص.
diff --git a/config/locales/doorkeeper.eo.yml b/config/locales/doorkeeper.eo.yml
index ce39f3bd1..59df52852 100644
--- a/config/locales/doorkeeper.eo.yml
+++ b/config/locales/doorkeeper.eo.yml
@@ -3,8 +3,10 @@ eo:
   activerecord:
     attributes:
       doorkeeper/application:
-        name: Nomo
-        redirect_uri: URI de plusendo
+        name: Aplikaĵa nomo
+        redirect_uri: Plusenda URI
+        scopes: Ampleksoj
+        website: Aplikaĵa retejo
     errors:
       models:
         doorkeeper/application:
@@ -18,7 +20,7 @@ eo:
     applications:
       buttons:
         authorize: Rajtigi
-        cancel: Rezigni
+        cancel: Nuligi
         destroy: Detrui
         edit: Redakti
         submit: Sendi
@@ -31,20 +33,24 @@ eo:
       help:
         native_redirect_uri: Uzu %{native_redirect_uri} por lokaj provoj
         redirect_uri: Uzu unu linion por ĉiu URI
-        scopes: Apartigu ampleksojn per spacetoj. Lasu malplena por uzi la senŝanĝajn ampleksojn.
+        scopes: Apartigu ampleksojn per spacetoj. Lasu malplena por uzi la dekomencajn ampleksojn.
       index:
-        callback_url: URL vokita per referenco
+        application: Aplikaĵo
+        callback_url: Revena URL
+        delete: Forigi
         name: Nomo
-        new: Nova Aplikaĵo
+        new: Nova aplikaĵo
+        scopes: Ampleksoj
+        show: Montri
         title: Viaj aplikaĵoj
       new:
         title: Nova aplikaĵo
       show:
         actions: Agoj
-        application_id: Identigo de la aplikaĵo
-        callback_urls: URL-j vokitaj per referenco
+        application_id: Klienta ŝlosilo
+        callback_urls: Revenaj URL-oj
         scopes: Ampleksoj
-        secret: Sekreto
+        secret: Klienta sekreto
         title: 'Aplikaĵo: %{name}'
     authorizations:
       buttons:
@@ -53,11 +59,11 @@ eo:
       error:
         title: Eraro okazis
       new:
-        able_to: Povos
+        able_to: Ĝi povos
         prompt: La aplikaĵo %{client_name} petas aliron al via konto
         title: Rajtigo bezonata
       show:
-        title: Copy this authorization code and paste it to the application.
+        title: Kopiu ĉi tiun rajtigan kodon kaj gluu ĝin al la aplikaĵo.
     authorized_applications:
       buttons:
         revoke: Malrajtigi
@@ -71,24 +77,24 @@ eo:
         title: Viaj rajtigitaj aplikaĵoj
     errors:
       messages:
-        access_denied: La posedanto de la rimedo aŭ la rajtiga servilo rifuzis vian peton.
+        access_denied: La posedanto de la rimedo aŭ de la rajtiga servilo rifuzis vian peton.
         credential_flow_not_configured: La sendado de la identigiloj de la posedanto de la rimedo malsukcesis ĉar Doorkeeper.configure.resource_owner_from_credentials ne estis agordita.
-        invalid_client: La aŭtentigo de la kliento malsukcesis ĉar la kliento estas nekonata, aŭ mankis peto aŭtentigi, aŭ la aŭtentig-metodo ne estas subtenata.
+        invalid_client: Klienta aŭtentigo malsukcesa pro nekonata kliento, neniu klienta aŭtentigo inkluzivita, aŭ nesubtenata aŭtentiga metodo.
         invalid_grant: La rajtiga konsento ne estas valida, ne plu estas valida, estis forigita, ne kongruas kun la plusenda URI uzita en la aŭtentiga peto, aŭ estis sendita al alia kliento.
-        invalid_redirect_uri: La plusenda URI uzita en estas valida.
+        invalid_redirect_uri: La plusenda URI uzita ne estas valida.
         invalid_request: Mankis al la peto nepra parametro, enhavas nesubtenatan parametran valoron, aŭ la peto simple estas misformita.
-        invalid_resource_owner: La donitaj identigiloj pri la posedanto de la rimedo ne estas validaj, aŭ tiu ne povas esti trovita.
-        invalid_scope: La petita amplekso ne estas valida, estas nekonata, aŭ estas misformita.
+        invalid_resource_owner: La donitaj identigiloj pri la posedanto de la rimedo ne estas validaj, aŭ tiu lasta ne povas esti trovita
+        invalid_scope: La petita amplekso estas nevalida, nekonata, aŭ misformita.
         invalid_token:
-          expired: La atingoĵetono eskiĝis.
-          revoked: La atingoĵetono estis rifuzita.
-          unknown: La atingoĵetono ne estas valida.
+          expired: La alira ĵetono eksvalidiĝis
+          revoked: La alira ĵetono estis malvalidigita
+          unknown: La alira ĵetono estas nevalida
         resource_owner_authenticator_not_configured: La posedanto de la rimedo ne povis esti trovita ĉar Doorkeeper.configure.resource_owner_authenticator ne estas agordita.
-        server_error: La rajtiga servilo rimarkis neatenditan kondiĉon, kiu malpermesis al ĝi plenumi la peton.
-        temporarily_unavailable: La rajtiga servilo ne povas nun plenumi la peton pro dumtempa superŝarĝo aŭ prizorgado de la servilo.
-        unauthorized_client: La kliento ne rajtas fari tian peton uzante tiun metodon.
+        server_error: La rajtiga servilo renkontis neatenditan problemon, kiu malpermesis al ĝi plenumi la peton.
+        temporarily_unavailable: La rajtiga servilo ne povas nun plenumi la peton pro dumtempa troŝarĝo aŭ servila prizorgado.
+        unauthorized_client: Kliento ne rajtas fari ĉi tian peton per ĉi tiu metodo.
         unsupported_grant_type: La tipo de la rajtiga konsento ne estas subtenata de la rajtiga servilo.
-        unsupported_response_type: La rajtiga servilo ne subtenas tian respondon.
+        unsupported_response_type: La rajtiga servilo ne subtenas ĉi tian respondon.
     flash:
       applications:
         create:
@@ -96,7 +102,7 @@ eo:
         destroy:
           notice: Aplikaĵo forigita.
         update:
-          notice: Aplikaĵo aktualigita.
+          notice: Aplikaĵo ĝisdatigita.
       authorized_applications:
         destroy:
           notice: Aplikaĵo malrajtigita.
diff --git a/config/locales/doorkeeper.es.yml b/config/locales/doorkeeper.es.yml
index 976fae82c..937ecd32a 100644
--- a/config/locales/doorkeeper.es.yml
+++ b/config/locales/doorkeeper.es.yml
@@ -3,7 +3,7 @@ es:
   activerecord:
     attributes:
       doorkeeper/application:
-        name: Nombre
+        name: Nombre de aplicación
         redirect_uri: URI para redirección
         scopes: Ámbitos
         website: Sitio web
diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml
index a6b58156a..a5d9b9e03 100644
--- a/config/locales/doorkeeper.fr.yml
+++ b/config/locales/doorkeeper.fr.yml
@@ -6,7 +6,7 @@ fr:
         name: Nom
         redirect_uri: L’URL de redirection
         scope: Portée
-        scopes: Étendue
+        scopes: Étendues
         website: Site web de l'application
     errors:
       models:
@@ -41,7 +41,7 @@ fr:
         delete: Effacer
         name: Nom
         new: Nouvelle application
-        scopes: Scopes
+        scopes: Portées
         show: Voir
         title: Vos applications
       new:
diff --git a/config/locales/doorkeeper.hu.yml b/config/locales/doorkeeper.hu.yml
index 54e732f0c..fa706e100 100644
--- a/config/locales/doorkeeper.hu.yml
+++ b/config/locales/doorkeeper.hu.yml
@@ -5,6 +5,8 @@ hu:
       doorkeeper/application:
         name: Név
         redirect_uri: Visszairányító URI
+        scopes: Hatáskör
+        website: Az alkalmazás weboldala
     errors:
       models:
         doorkeeper/application:
@@ -33,9 +35,13 @@ hu:
         redirect_uri: Egy sor URI-nként
         scopes: A nézeteket szóközzel válaszd el. Hagyd üresen az alapértelmezett nézetekhez.
       index:
+        application: Alkalmazás
         callback_url: Callback URL
+        delete: Eltávolítás
         name: Név
         new: Új alkalmazás
+        scopes: Hatáskör
+        show: Mutat
         title: Alkalmazásod
       new:
         title: Új alkalmazás
@@ -67,6 +73,7 @@ hu:
         application: Alkalmazás
         created_at: Készítve
         date_format: "%Y-%m-%d %H:%M:%S"
+        scopes: Hatáskör
         title: Engedélyezett alkalmazásaid
     errors:
       messages:
diff --git a/config/locales/doorkeeper.no.yml b/config/locales/doorkeeper.no.yml
index 5b4dc9d6c..56c15fab7 100644
--- a/config/locales/doorkeeper.no.yml
+++ b/config/locales/doorkeeper.no.yml
@@ -5,6 +5,7 @@
       doorkeeper/application:
         name: Navn
         redirect_uri: Omdirigerings-URI
+        scopes: Omfang
         website: Applikasjonsnettside
     errors:
       models:
@@ -39,6 +40,7 @@
         delete: Fjern
         name: Navn
         new: Ny applikasjon
+        scopes: Omfang
         show: Vis
         title: Dine applikasjoner
       new:
diff --git a/config/locales/doorkeeper.ru.yml b/config/locales/doorkeeper.ru.yml
index 2234a9bbe..05c3d971c 100644
--- a/config/locales/doorkeeper.ru.yml
+++ b/config/locales/doorkeeper.ru.yml
@@ -5,6 +5,8 @@ ru:
       doorkeeper/application:
         name: Название
         redirect_uri: URI перенаправления
+        scopes: Права
+        website: Веб-сайт приложения
     errors:
       models:
         doorkeeper/application:
@@ -33,9 +35,13 @@ ru:
         redirect_uri: Используйте по одной строке на URI
         scopes: Разделяйте список разрешений пробелами. Оставьте незаполненным для использования разрешений по умолчанию.
       index:
+        application: Приложение
         callback_url: Callback URL
+        delete: Удалить
         name: Название
         new: Новое Приложение
+        scopes: Права
+        show: Показывать
         title: Ваши приложения
       new:
         title: Новое Приложение
diff --git a/config/locales/doorkeeper.sk.yml b/config/locales/doorkeeper.sk.yml
index f33f0a9e9..b8fd281f7 100644
--- a/config/locales/doorkeeper.sk.yml
+++ b/config/locales/doorkeeper.sk.yml
@@ -91,8 +91,8 @@ sk:
           unknown: Prístupový token je neplatný
         resource_owner_authenticator_not_configured: Resource Owner zlyhal pretože Doorkeeper.configure.resource_owner_authenticator nebol nakonfigurovaný.
         server_error: Nastala neočakávaná chyba na autorizačnom serveri ktorá zabránila vykonať požiadavku.
-        temporarily_unavailable: Autorizačný server Vás teraz nemôže obslúžiť pretože prebieha údržba alebo je dočasne preťažený.
-        unauthorized_client: Klient nie je autorizovaný vykonať túto požiadavku touto metódou.
+        temporarily_unavailable: Autorizačný server ťa teraz nemôže obslúžiť, pretože prebieha údržba alebo je dočasne preťažený.
+        unauthorized_client: Klient nie je autorizovaný vykonať danú požiadavku takouto metódou.
         unsupported_grant_type: Tento typ oprávnenia nie je podporovaný autorizačným serverom.
         unsupported_response_type: Autorizačný server nepodporuje typ tejto odpovede.
     flash:
@@ -115,5 +115,5 @@ sk:
         title: Požadovaná OAuth autorizácia
     scopes:
       follow: sledovať, blokovať, povoliť a zušiť sledovanie účtov
-      read: prezrieť váš účet
+      read: prezrieť dáta na vašom účete
       write: poslať vo vašom mene
diff --git a/config/locales/doorkeeper.sv.yml b/config/locales/doorkeeper.sv.yml
index 19a457427..342198bd3 100644
--- a/config/locales/doorkeeper.sv.yml
+++ b/config/locales/doorkeeper.sv.yml
@@ -36,7 +36,7 @@ sv:
         scopes: Separera omfattningen med mellanslag. Lämna tomt för att använda standardomfattning.
       index:
         application: Applikation
-        callback_url: Callback URL
+        callback_url: Återkalls URL
         delete: Ta bort
         name: Namn
         new: Ny applikation
@@ -48,10 +48,10 @@ sv:
       show:
         actions: Handlingar
         application_id: Klientnyckel
-        callback_urls: Callback URLs
+        callback_urls: Återkalls URLs
         scopes: Omfattning
         secret: Kundhemlighet
-        title: 'Application: %{name}'
+        title: 'Program: %{name}'
     authorizations:
       buttons:
         authorize: Godkänna
@@ -110,7 +110,7 @@ sv:
       admin:
         nav:
           applications: Applikationer
-          oauth2_provider: OAuth2 Provider
+          oauth2_provider: OAuth2 leverantör
       application:
         title: OAuth-behörighet krävs
     scopes:
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 7c3cd922b..e5e3ddf62 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -23,7 +23,6 @@ en:
       real_conversation_title: Built for real conversation
       within_reach_body: Multiple apps for iOS, Android, and other platforms thanks to a developer-friendly API ecosystem allow you to keep up with your friends anywhere.
       within_reach_title: Always within reach
-    find_another_instance: Find another instance
     generic_description: "%{domain} is one server in the network"
     hosted_on: Mastodon hosted on %{domain}
     learn_more: Learn more
@@ -274,6 +273,9 @@ en:
       contact_information:
         email: Business e-mail
         username: Contact username
+      hero:
+        desc_html: Displayed on the frontpage. At least 600x100px recommended. When not set, falls back to instance thumbnail
+        title: Hero image
       peers_api_enabled:
         desc_html: Domain names this instance has encountered in the fediverse
         title: Publish list of discovered instances
@@ -290,6 +292,9 @@ en:
         open:
           desc_html: Allow anyone to create an account
           title: Open registration
+      show_known_fediverse_at_about_page:
+        desc_html: When toggled, it will show toots from all the known fediverse on preview. Otherwise it will only show local toots.
+        title: Show known fediverse on timeline preview
       show_staff_badge:
         desc_html: Show a staff badge on a user page
         title: Show staff badge
@@ -354,7 +359,8 @@ en:
     your_token: Your access token
   auth:
     agreement_html: By signing up you agree to follow <a href="%{rules_path}">the rules of the instance</a> and <a href="%{terms_path}">our terms of service</a>.
-    change_password: Security
+    change_password: Password
+    confirm_email: Confirm email
     delete_account: Delete account
     delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
     didnt_get_confirmation: Didn't receive confirmation instructions?
@@ -364,9 +370,16 @@ en:
     logout: Logout
     migrate_account: Move to a different account
     migrate_account_html: If you wish to redirect this account to a different one, you can <a href="%{path}">configure it here</a>.
+    or: or
+    or_log_in_with: Or log in with
+    providers:
+      cas: CAS
+      saml: SAML
     register: Sign up
+    register_elsewhere: Sign up on another server
     resend_confirmation: Resend confirmation instructions
     reset_password: Reset password
+    security: Security
     set_new_password: Set new password
   authorize_follow:
     error: Unfortunately, there was an error looking up the remote account
@@ -413,6 +426,13 @@ en:
       title: This page is not correct
     noscript_html: To use the Mastodon web application, please enable JavaScript. Alternatively, try one of the <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">native apps</a> for Mastodon for your platform.
   exports:
+    archive_takeout:
+      date: Date
+      download: Download your archive
+      hint_html: You can request an archive of your <strong>toots and uploaded media</strong>. The exported data will be in ActivityPub format, readable by any compliant software.
+      in_progress: Compiling your archive...
+      request: Request your archive
+      size: Size
     blocks: You block
     csv: CSV
     follows: You follow
@@ -537,7 +557,9 @@ en:
           trillion: T
           unit: ''
   pagination:
+    newer: Newer
     next: Next
+    older: Older
     prev: Prev
     truncate: "&hellip;"
   preferences:
@@ -734,6 +756,10 @@ en:
     setup: Set up
     wrong_code: The entered code was invalid! Are server time and device time correct?
   user_mailer:
+    backup_ready:
+      explanation: You requested a full backup of your Mastodon account. It's now ready for download!
+      subject: Your archive is ready for download
+      title: Archive takeout
     welcome:
       edit_profile_action: Setup profile
       edit_profile_step: You can customize your profile by uploading an avatar, header, changing your display name and more. If you’d like to review new followers before they’re allowed to follow you, you can lock your account.
@@ -755,4 +781,5 @@ en:
   users:
     invalid_email: The e-mail address is invalid
     invalid_otp_token: Invalid two-factor code
+    seamless_external_login: You are logged in via an external service, so password and e-mail settings are not available.
     signed_in_as: 'Signed in as:'
diff --git a/config/locales/eo.yml b/config/locales/eo.yml
index bc259957d..8bc2aaa41 100644
--- a/config/locales/eo.yml
+++ b/config/locales/eo.yml
@@ -1,15 +1,32 @@
 ---
 eo:
   about:
-    about_mastodon_html: Mastodon estas <em>senpaga, malfermitkoda</em> socia reto. Ĝi estas <em>sencentra</em> alia eblo al komercaj servoj. Ĝi evitigas, ke unusola firmao regu vian tutan komunikadon. Elektu servilon, kiun vi fidas. Kiu ajn estas via elekto, vi povas interagi kun ĉiuj aliaj uzantoj. Iu ajn povas krei sian propran aperaĵon de Mastodon en sia servilo, kaj partopreni en la <em>socia reto</em> tute glate.
-    about_this: Pri tiu aperaĵo
+    about_hashtag_html: Ĉi tiuj estas la publikaj mesaĝoj markitaj per <strong>#%{hashtag}</strong>. Vi povas interagi kun ili se vi havas konton ie ajn en la fediverse.
+    about_mastodon_html: Mastodon estas socia reto bazita sur malfermitaj retaj protokoloj kaj sur libera malfermitkoda programo. Ĝi estas sencentra kiel retmesaĝoj.
+    about_this: Pri
+    closed_registrations: Registriĝoj estas nuntempe fermitaj en ĉi tiu nodo. Tamen, vi povas trovi alian nodon por fari konton kaj aliri al la sama reto de tie.
     contact: Kontakti
+    contact_missing: Ne elektita
+    contact_unavailable: Ne disponebla
     description_headline: Kio estas %{domain}?
-    domain_count_after: aliaj aperaĵoj
+    domain_count_after: aliaj nodoj
     domain_count_before: Konektita al
-    hosted_on: Mastodon gastigita sur %{domain}
-    learn_more: Lernu pli
-    other_instances: Aliaj aperaĵoj
+    extended_description_html: |
+      <h3>Bona loko por reguloj</h3>
+      <p>La detala priskribo ne estis elektita.</p>
+    features:
+      humane_approach_body: Lernante de eraroj de aliaj retoj, Mastodon celas fari etikajn fasonajn elektojn por batali kontraŭ misuzado de sociaj retoj.
+      humane_approach_title: Aliro pli humana
+      not_a_product_body: Mastodon ne estas komerca reto. Neniu reklamo, neniu kolektado de datumoj, neniu privilegio. Ne estas centra aŭtoritato.
+      not_a_product_title: Vi estas homo, ne produkto
+      real_conversation_body: Per 500 disponeblaj signoj, per elektebloj pri videbleco, kaj per avertoj pri enhavo, vi povas esprimi vin tiel, kiel vi volas.
+      real_conversation_title: Konstruita por veraj konversacioj
+      within_reach_body: Pluraj aplikaĵoj por iOS, Android, kaj aliaj platformoj danke al API-medio bonveniga por programistoj permesas resti en kontakto kun viaj amikoj ĉie.
+      within_reach_title: Ĉiam kontaktebla
+    generic_description: "%{domain} estas unu servilo en la reto"
+    hosted_on: Mastodon gastigita en %{domain}
+    learn_more: Lerni pli
+    other_instances: Listo de nodoj
     source_code: Fontkodo
     status_count_after: mesaĝoj
     status_count_before: Kiu publikigis
@@ -20,250 +37,358 @@ eo:
     follow: Sekvi
     followers: Sekvantoj
     following: Sekvatoj
-    media: Kumunikiloj
+    media: Aŭdovidaĵoj
+    moved_html: "%{name} moviĝis al %{new_profile_link}:"
     nothing_here: Estas nenio ĉi tie!
     people_followed_by: Sekvatoj de %{name}
     people_who_follow: Sekvantoj de %{name}
     posts: Mesaĝoj
-    posts_with_replies: Tootoj kun respondaj
+    posts_with_replies: Mesaĝoj kaj respondoj
     remote_follow: Fore sekvi
-    reserved_username: La usantnomo estas reservis
+    reserved_username: La uzantnomo estas rezervita
     roles:
       admin: Administranto
-    unfollow: Malsekvi
+      moderator: Kontrolanto
+    unfollow: Ne plu sekvi
   admin:
+    account_moderation_notes:
+      account: Kontrolanto
+      create: Krei
+      created_at: Dato
+      created_msg: Kontrola noto sukcese kreita!
+      delete: Forigi
+      destroyed_msg: Kontrola noto sukcese detruita!
     accounts:
-      are_you_sure: Ĉu vi certe?
-      confirm: Confirmi
-      confirmed: Confirmis
+      are_you_sure: Ĉu vi certas?
+      by_domain: Domajno
+      confirm: Konfirmi
+      confirmed: Konfirmita
+      demote: Degradi
+      disable: Malebligi
       disable_two_factor_authentication: Malebligi 2FA
-      display_name: Montri nomo
+      disabled: Malebligita
+      display_name: Montrata nomo
       domain: Domajno
       edit: Redakti
       email: Retpoŝto
+      enable: Ebligi
+      enabled: Ebligita
+      feed_url: URL de la fluo
       followers: Sekvantoj
-      followers_url: Sekvantoj URL
+      followers_url: URL de la sekvantoj
       follows: Sekvatoj
+      inbox_url: Enira URL
       ip: IP
       location:
         all: Ĉio
         local: Loka
         remote: Fora
         title: Loko
-      media_attachments: Komunkiloj kunsendaĵo
+      login_status: Ensaluta stato
+      media_attachments: Ligitaj aŭdovidaĵoj
+      memorialize: Ŝanĝi al memoro
       moderation:
         all: Ĉio
-        silenced: Silentis
-        suspended: Suspendis
-        title: Moderulo
-      most_recent_activity: Ple freŝa aktiveco
-      most_recent_ip: Ple freŝa IP
-      not_subscribed: Ne abonis
+        silenced: Silentigitaj
+        suspended: Haltigitaj
+        title: Kontrolado
+      moderation_notes: Kontrolaj notoj
+      most_recent_activity: Lasta ago
+      most_recent_ip: Lasta IP
+      not_subscribed: Ne abonita
       order:
-        alphabetic: Alfabetiko
-        most_recent: Ple freŝa
-        title: Ordono
-      perform_full_suspension: Fari kompleta suspendi
-      profile_url: Profilo URL
+        alphabetic: Laŭalfabete
+        most_recent: Plej lastatempa
+        title: Ordo
+      outbox_url: Elira URL
+      perform_full_suspension: Tute haltigi
+      profile_url: Profila URL
+      promote: Plirangigi
       protocol: Protokolo
       public: Publika
-      push_subscription_expires: PuSH subscription expires
-      redownload: Refreŝigi avataro
+      push_subscription_expires: Eksvalidiĝo de la abono al PuSH
+      redownload: Aktualigi profilbildon
       reset: Restarigi
-      reset_password: Restarigi pasvorto
+      reset_password: Restarigi pasvorton
       resubscribe: Reaboni
-      salmon_url: Salmon URL
+      role: Permesoj
+      roles:
+        admin: Administranto
+        moderator: Kontrolanto
+        staff: Teamo
+        user: Uzanto
+      salmon_url: Salmon-URL
       search: Serĉi
-      shared_inbox_url: Shared Inbox URL
+      shared_inbox_url: URL de kunhavigita leterkesto
       show:
-        created_reports: Raportoj kreita de ĉi tiu konto
-        report: raporto
-        targeted_reports: Raportoj kreita al ĉi tiu konton
-      silence: Silenti
-      statuses: Statusoj
+        created_reports: Signaloj kreitaj de ĉi tiu konto
+        report: signalo
+        targeted_reports: Signaloj kreitaj de ĉi tiu konto
+      silence: Kaŝi
+      statuses: Mesaĝoj
       subscribe: Aboni
       title: Kontoj
-      undo_silenced: Malfari silenti
-      undo_suspension: Malfari suspendi
+      undo_silenced: Malfari kaŝon
+      undo_suspension: Malfari haltigon
       unsubscribe: Malaboni
       username: Uzantnomo
-      web: Ret
+      web: Reto
+    action_logs:
+      actions:
+        confirm_user: "%{name} konfirmis retadreson de uzanto %{target}"
+        create_custom_emoji: "%{name} alŝutis novan emoĝion %{target}"
+        create_domain_block: "%{name} blokis domajnon %{target}"
+        create_email_domain_block: "%{name} metis en nigran liston domajnon %{target}"
+        demote_user: "%{name} degradis uzanton %{target}"
+        destroy_domain_block: "%{name} malblokis domajnon %{target}"
+        destroy_email_domain_block: "%{name} metis en blankan liston domajnon %{target}"
+        destroy_status: "%{name} forigis mesaĝojn de %{target}"
+        disable_2fa_user: "%{name} malebligis dufaktoran aŭtentigon por uzanto %{target}"
+        disable_custom_emoji: "%{name} malebligis emoĝion %{target}"
+        disable_user: "%{name} malebligis ensaluton por uzanto %{target}"
+        enable_custom_emoji: "%{name} ebligis emoĝion %{target}"
+        enable_user: "%{name} ebligis ensaluton por uzanto %{target}"
+        memorialize_account: "%{name} ŝanĝis la konton de %{target} al memora paĝo"
+        promote_user: "%{name} plirangigis uzanton %{target}"
+        reset_password_user: "%{name} restarigis pasvorton de uzanto %{target}"
+        resolve_report: "%{name} flankmetis signalon %{target}"
+        silence_account: "%{name} kaŝis la konton de %{target}"
+        suspend_account: "%{name} haltigis la konton de %{target}"
+        unsilence_account: "%{name} malkaŝis la konton de %{target}"
+        unsuspend_account: "%{name} malhaltigis la konton de %{target}"
+        update_custom_emoji: "%{name} ĝisdatigis emoĝion %{target}"
+        update_status: "%{name} ĝisdatigis mesaĝon de %{target}"
+      title: Kontrola protokolo
     custom_emojis:
-      copied_msg: Sukcese kreis loka kopio de la emojio
-      copy: Kopi
-      copy_failed_msg: Could not make a local copy of that emoji
-      created_msg: Emojio estas kreita sukcesa!
+      by_domain: Domajno
+      copied_msg: Loka kopio de la emoĝio sukcese kreita
+      copy: Kopii
+      copy_failed_msg: Fari lokan kopion de ĉi tiu emoĝio ne eblis
+      created_msg: Emoĝio sukcese kreita!
       delete: Forigi
-      destroyed_msg: Emojio estas forigis sukcesa!
+      destroyed_msg: Emoĝio sukcese forigita!
       disable: Malebligi
-      disabled_msg: Emojio estas malebligis sukcesa
-      emoji: Emojio
+      disabled_msg: Emoĝio sukcese malebligita
+      emoji: Emoĝio
       enable: Ebligi
-      enabled_msg: Emojio estas ebligis sukcesa
+      enabled_msg: Tiu emoĝio estis sukcese ebligita
       image_hint: PNG ĝis 50KB
+      listed: Listigita
       new:
-        title: Aldoni nova kutimo emojio
-      shortcode: Malongakodo
-      shortcode_hint: At least 2 characters, only alphanumeric characters and underscores
-      title: Kutimoj emojioj
+        title: Aldoni novan propran emoĝion
+      overwrite: Anstataŭigi
+      shortcode: Mallonga kodo
+      shortcode_hint: Almenaŭ 2 signoj, nur literoj, ciferoj kaj substrekoj
+      title: Propraj emoĝioj
+      unlisted: Nelistigita
+      update_failed_msg: Ĝisdatigi tiun emoĝion ne eblis
+      updated_msg: Emoĝio sukcese ĝisdatigita!
       upload: Alŝuti
     domain_blocks:
-      add_new: Aldoni novo
-      created_msg: Domajno bloko nun estas procesita
-      destroyed_msg: Domajno bloko estas malfaris
+      add_new: Aldoni novan
+      created_msg: Domajna blokado en traktado
+      destroyed_msg: Domajna blokado malfarita
       domain: Domajno
       new:
-        create: Krei bloko
+        create: Krei blokadon
+        hint: La domajna blokado ne evitigos kreadon de novaj kontoj en la datumbazo, sed aplikos specifajn kontrolajn agojn sur ĉi tiujn kontojn aŭtomate kaj retroaktive.
         severity:
+          desc_html: "<strong>Kaŝi</strong> igos la mesaĝojn de la konto nevideblaj al tiuj, kiuj ne sekvas tiun. <strong>Haltigi</strong> forigos ĉiujn enhavojn, aŭdovidaĵojn kaj datumojn de la konto. Uzu <strong>Nenio</strong> se vi simple volas malakcepti aŭdovidaĵojn."
           noop: Nenio
-          silence: Silenti
-          suspend: Suspendi
-        title: Nova domajno bloko
-      reject_media: Reject media files
+          silence: Kaŝi
+          suspend: Haltigi
+        title: Nova domajna blokado
+      reject_media: Malakcepti aŭdovidajn dosierojn
+      reject_media_hint: Forigas aŭdovidaĵojn loke konservitajn kaj rifuzas alŝuti ajnan estonte. Senzorge pri haltigoj
       severities:
         noop: Nenio
-        silence: Silenti
-        suspend: Suspendi
+        silence: Kaŝi
+        suspend: Haltigi
       severity: Severeco
       show:
         affected_accounts:
-          one: Unu konto en la datumbazo esta afekta
-          other: "%{count} kontoj en la datumbazo esta afekta"
+          one: Unu konto en la datumbazo esta influita
+          other: "%{count} kontoj en la datumbazo estas influitaj"
         retroactive:
-          silence: Malfari silenti ĉio konton de ĉi tiu domajno
-          suspend: Malfari suspendi ĉio konton de ĉi tiu domajno
-        title: Malfari domajno bloko por %{domain}
+          silence: Malkaŝi ĉiujn kontojn, kiuj ekzistas en ĉi tiu domajno
+          suspend: Malhaltigi ĉiujn kontojn, kiuj ekzistas en ĉi tiu domajno
+        title: Malfari domajnan blokadon por %{domain}
         undo: Malfari
-      title: Domajnoj blokoj
+      title: Domajnaj blokadoj
       undo: Malfari
     email_domain_blocks:
-      add_new: Aldoni novo
-      created_msg: Retpoŝto domajno bloko estas kreita sukcesa
+      add_new: Aldoni novan
+      created_msg: Retadreso sukcese aldonita al la nigra listo
       delete: Forigi
-      destroyed_msg: Retpoŝto domajno bloko estas foriga sukcesa
+      destroyed_msg: Retadreso sukcese forigita de la nigra listo
       domain: Domajno
       new:
-        create: Aldoni bloko
-        title: Nova retpoŝto domajno bloko
-      title: Retpoŝto domajno bloko
+        create: Aldoni domajnon
+        title: Nova blokado de retadresa domajno
+      title: Nigra listo de retadresaj domajnoj
     instances:
       account_count: Konataj kontoj
       domain_name: Domajno
       reset: Restarigi
       search: Serĉi
-      title: Konataj petskriboj
+      title: Konataj nodoj
+    invites:
+      filter:
+        all: Ĉio
+        available: Disponebla
+        expired: Eksvalida
+        title: Filtri
+      title: Invitoj
     reports:
-      action_taken_by: Action taken by
-      are_you_sure: Ĉu vi certe?
+      action_taken_by: Ago farita de
+      are_you_sure: Ĉu vi certas?
       comment:
-        label: komento
+        label: Komento
         none: Nenio
       delete: Forigi
       id: ID
       mark_as_resolved: Marki kiel solvita
       nsfw:
-        'false': Ne kaŝi kumunikiloj kunsendaĵoj
-        'true': Kaŝi kumunikiloj kunsendaĵoj
-      report: 'Raporto #%{id}'
+        'false': Malkaŝi aŭdovidajn kunsendaĵojn
+        'true': Kaŝi aŭdovidajn kunsendaĵojn
+      report: 'Signalo #%{id}'
       report_contents: Enhavo
-      reported_account: Raportis konto
-      reported_by: Raporta de
+      reported_account: Signalita konto
+      reported_by: Signalita de
       resolved: Solvita
-      silence_account: Silenti konton
-      status: Statusoj
-      suspend_account: Suspendi konton
+      silence_account: Kaŝi konton
+      status: Mesaĝoj
+      suspend_account: Haltigi konton
       target: Celo
-      title: Raportoj
-      unresolved: Ne solvita
+      title: Signaloj
+      unresolved: Nesolvita
       view: Vidi
     settings:
+      activity_api_enabled:
+        desc_html: Sumo de lokaj mesaĝoj, aktivaj uzantoj, kaj novaj registriĝoj laŭsemajne
+        title: Publikigi kunmetitajn statistikojn pri la uzanta agado
       bootstrap_timeline_accounts:
-        desc_html: Disigi multaj uzantnomoj per komo. Nur lokaj kaj malsloŝi kontoj estus operaci. Defaŭlo Defaŭlo kiam malplena estas ĉio lokaj administristoj.
-        title: Defaŭltoj sakvatoj al novoj uzantoj
+        desc_html: Disigi plurajn uzantnomojn per komo. Funkcios nur por lokaj neŝlositaj kontoj. Kiam malplena, la dekomenca valoro estas ĉiuj lokaj administrantoj.
+        title: Dekomencaj sekvadoj por novaj uzantoj
       contact_information:
-        email: Afero retpoŝto
-        username: kontakto uzantnomo
+        email: Publika retadreso
+        username: Kontakta uzantnomo
+      hero:
+        desc_html: Montrata en la ĉefpaĝo. Almenaŭ 600x100px rekomendita. Kiam ne agordita, la bildeto de la nodo estos uzata
+        title: Kapbildo
+      peers_api_enabled:
+        desc_html: Nomoj de domajnoj, kiujn ĉi tiu nodo renkontis en la fediverse
+        title: Publikigi liston de malkovritaj nodoj
       registrations:
         closed_message:
-          desc_html: Vidigis sur antaŭpaĝo kian registrado estas fermis. Vi povas uzi HTML
-          title: Fermis registrado mesaĝo
+          desc_html: Montrita sur la hejma paĝo kiam registriĝoj estas fermitaj. Vi povas uzi HTML-etikedojn
+          title: Mesaĝo pri fermitaj registriĝoj
         deletion:
-          desc_html: Permesi ĉiuj forigi ilian konton
-          title: Malfermi konto forigo
+          desc_html: Permesi al iu ajn forigi propran konton
+          title: Permesi forigi konton
+        min_invite_role:
+          disabled: Neniu
+          title: Permesi invitojn de
         open:
-          desc_html: Permesi ĉiuj krei konto
-          title: Malfermi registrado
+          desc_html: Permesi iun ajn krei konton
+          title: Malfermi registriĝojn
+      show_known_fediverse_at_about_page:
+        desc_html: Kiam ŝaltita, ĝi montros mesaĝojn de la tuta konata fediverse antaŭvide. Aliokaze, ĝi montros nur lokajn mesaĝojn.
+        title: Montri konatan fediverse en tempolinia antaŭvido
+      show_staff_badge:
+        desc_html: Montri teaman insignon en paĝo de uzanto
+        title: Montri teaman insignon
       site_description:
-        title: Priskribo de petskribo
+        desc_html: Enkonduka alineo en la ĉefpaĝo kaj en informaj etikedoj. Vi povas uzi HTML-etikedojn, kiel <code>&lt;a&gt;</code> kaj <code>&lt;em&gt;</code>.
+        title: Priskribo de la nodo
       site_description_extended:
-        title: Kutimo etendis informaĵo
+        desc_html: Bona loko por viaj sintenaj reguloj, aliaj reguloj, gvidlinioj kaj aliaj aferoj, kiuj apartigas vian nodon. Vi povas uzi HTML-etikedojn
+        title: Propraj detalaj informoj
       site_terms:
-        desc_html: Vi povas skribi via politika pri privateco reguloj de servo aŭ aliaj senpagaj. Vi povas uzi HTML
-        title: Kutimoj reguloj de servo
-      site_title: Petskribo nomo
+        desc_html: Vi povas skribi vian propran privatecan politikon, viajn uzkondiĉojn aŭ aliajn leĝaĵojn. Vi povas uzi HTML-etikedojn
+        title: Propraj uzkondiĉoj
+      site_title: Nomo de la nodo
       thumbnail:
-        desc_html: Uzis por antaŭvido vojo OpenGraph kaj API. 1200x630px rekomendis
-        title: Bildeto de petskribo
+        desc_html: Uzata por antaŭvidoj per OpenGraph kaj per API. 1200x630px rekomendita
+        title: Bildeto de la nodo
       timeline_preview:
-        desc_html: Vidigi publika tempolinio sur surteriĝo paĝo
-        title: Antaŭvido de tempolinio
-      title: Retparaĝoj preferoj
+        desc_html: Montri publikan tempolinion en komenca paĝo
+        title: Tempolinia antaŭvido
+      title: Retejaj agordoj
     statuses:
-      back_to_account: Irienigi al konton paĝon
+      back_to_account: Reveni al konta paĝo
       batch:
         delete: Forigi
         nsfw_off: Malŝalti NSFW
         nsfw_on: Ŝalti NSFW
-      execute: Execute
-      failed_to_execute: Failed to execute
+      execute: Ekigi
+      failed_to_execute: Ekigo malsukcesa
       media:
-        hide: Kaŝi kumunikiloj
-        show: Vidigi kumunikiloj
-        title: Kumunikiloj
-      no_media: Neniu Kumunikilo
-      title: Kontoj statusoj
-      with_media: Kun kumunikiloj
+        hide: Kaŝi aŭdovidaĵojn
+        show: Montri aŭdovidaĵojn
+        title: Aŭdovidaĵoj
+      no_media: Neniu aŭdovidaĵo
+      title: Mesaĝoj de la konto
+      with_media: Kun aŭdovidaĵoj
     subscriptions:
-      callback_url: Callback URL
-      confirmed: Confirmis
-      expires_in: Finiĝus en
-      last_delivery: plej freŝa transdono
+      callback_url: Revena URL
+      confirmed: Konfirmita
+      expires_in: Eksvalidiĝas en
+      last_delivery: Lasta livero
       title: WebSub
-      topic: Topic
-    title: Administri
+      topic: Temo
+    title: Administrado
   admin_mailer:
     new_report:
-      body: "%{reporter} raportis %{target}"
-      subject: Nova raporto por %{instance} (#%{id})
+      body: "%{reporter} signalis %{target}"
+      subject: Nova signalo por %{instance} (#%{id})
   application_mailer:
-    settings: 'Ŝanĝi la retpoŝt-mesaĝajn preferojn: %{link}'
+    notification_preferences: Ŝanĝi retmesaĝajn preferojn
+    salutation: "%{name},"
+    settings: 'Ŝanĝi retmesaĝajn preferojn: %{link}'
     view: 'Vidi:'
+    view_profile: Vidi profilon
+    view_status: Vidi mesaĝon
   applications:
-    created: Aplikaĵo sukcesa kreis
-    destroyed: Aplikaĵo sukcesa forigis
+    created: Aplikaĵo sukcese kreita
+    destroyed: Aplikaĵo sukcese forigita
     invalid_url: La URL donita ne estas valida
-    regenerate_token: Regeneri aliron signon
-    token_regenerated: Aliro signo regeneris sukcese
-    your_token: Via aliro signo
+    regenerate_token: Rekrei aliran ĵetonon
+    token_regenerated: Alira ĵetono sukcese rekreita
+    warning: Estu tre atenta kun ĉi tiu datumo. Neniam diskonigu ĝin al iu ajn!
+    your_token: Via alira ĵetono
   auth:
-    change_password: Ŝanĝi pasvorton
+    agreement_html: Per registriĝo, vi konsentas kun <a href="%{rules_path}">la reguloj de la nodo</a> kaj <a href="%{terms_path}">niaj uzkondiĉoj</a>.
+    change_password: Pasvorto
+    confirm_email: Konfirmi retadreson
     delete_account: Forigi konton
+    delete_account_html: Se vi deziras forigi vian konton, vi povas <a href="%{path}">fari tion ĉi tie</a>. Vi bezonos konfirmi vian peton.
     didnt_get_confirmation: Ĉu vi ne ricevis la instrukciojn por konfirmi?
     forgot_password: Pasvorto forgesita?
+    invalid_reset_password_token: Ĵetono por restarigi pasvorton nevalida aŭ eksvalida. Bonvolu peti novan.
     login: Ensaluti
     logout: Elsaluti
-    register: Membriĝi
+    migrate_account: Movi al alia konto
+    migrate_account_html: Se vi deziras alidirekti ĉi tiun konton al alia, vi povas <a href="%{path}">agordi ĝin ĉi tie</a>.
+    or: aŭ
+    or_log_in_with: Aŭ ensaluti per
+    providers:
+      cas: CAS
+      saml: SAML
+    register: Registriĝi
+    register_elsewhere: Registriĝi en alia servilo
     resend_confirmation: Resendi la instrukciojn por konfirmi
-    reset_password: Ŝanĝi la pasvorton
+    reset_password: Ŝanĝi pasvorton
+    security: Sekureco
     set_new_password: Elekti novan pasvorton
   authorize_follow:
-    error: Bedaŭrinde, okazis eraro provante konsulti la foran konton
+    error: Bedaŭrinde, estis eraro en la serĉado de la fora konto
     follow: Sekvi
-    follow_request: 'Vi sendis sekvatin peton al:'
-    following: 'Sukceso! Vi nun sekvi:'
+    follow_request: 'Vi sendis peton de sekvado al:'
+    following: 'Sukceson! Vi nun sekvas:'
     post_follow:
-      close: Aŭ, Vi justa povas fermi ĉi tion.
-      return: Ilienigi al la uzantoan profilon
+      close: Aŭ, vi povas simple fermi ĉi tiun fenestron.
+      return: Reveni al la profilo de uzanto
       web: Iri al reto
     title: Sekvi %{acct}
   datetime:
@@ -272,164 +397,382 @@ eo:
       about_x_months: "%{count}mo"
       about_x_years: "%{count}j"
       almost_x_years: "%{count}j"
-      half_a_minute: Ĵus
+      half_a_minute: Ĵuse
       less_than_x_minutes: "%{count}m"
-      less_than_x_seconds: Ĵus
+      less_than_x_seconds: Ĵuse
       over_x_years: "%{count}j"
       x_days: "%{count}t"
       x_minutes: "%{count}m"
       x_months: "%{count}mo"
       x_seconds: "%{count}s"
+  deletes:
+    bad_password_msg: Malĝusta pasvorto
+    confirm_password: Enmetu vian nunan pasvorton por konfirmi vian identecon
+    description_html: Tio <strong>porĉiame kaj neŝanĝeble</strong> forigos la enhavon de via konto kaj malaktivigos ĝin. Via uzantnomo restos rezervita por eviti postajn trompojn pri identeco.
+    proceed: Forigi konton
+    success_msg: Via konto estis sukcese forigita
+    warning_html: La forigo de la enhavo estas certa nur por ĉi tiu aparta nodo. Enhavo, kiu estis disvastigita verŝajne lasos spurojn. Eksterretaj serviloj kaj serviloj, kiuj ne abonas viajn ĝisdatigojn ne ĝisdatigos siajn datumbazojn.
+    warning_title: Disponebleco de disvastigita enhavo
+  errors:
+    '403': Vi ne havas la rajton por vidi ĉi tiun paĝon.
+    '404': La paĝo, kiun vi serĉas, ne ekzistas.
+    '410': La paĝo, kiun vi serĉas, ne plu ekzistas.
+    '422':
+      content: Sekureca konfirmo malsukcesa. Ĉu vi blokas kuketojn?
+      title: Sekureca konfirmo malsukcesa
+    '429': Datumtrafiko limigita
+    '500':
+      content: Ni bedaŭras, io malsukcesis niaflanke.
+      title: Ĉi tiu paĝo ne estas ĝusta
+    noscript_html: |-
+      Por uzi la retan aplikaĵon de Mastodon, bonvolu ebligi JavaScript. Alimaniere, provu unu el la
+      <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">operaciumaj aplikaĵoj</a> por Mastodon por via platformo.
   exports:
-    blocks: Via blokoj
+    archive_takeout:
+      date: Dato
+      download: Elŝuti vian arkivon
+      hint_html: Vi povas peti arkivon de viaj <strong>mesaĝoj kaj alŝutitaj aŭdovidaĵoj</strong>. La eksportitaj datumoj estos en la formato ActivityPub, legebla de ajna konformema programo.
+      in_progress: Kunmetado de via arkivo…
+      request: Peti vian arkivon
+      size: Grandeco
+    blocks: Vi blokas
     csv: CSV
-    follows: Via sekvatoj
-    mutes: Via silentoj
-    storage: Mediaĵa konservado
+    follows: Vi sekvas
+    mutes: Vi silentigas
+    storage: Aŭdovidaĵa konservado
   followers:
     domain: Domajno
-    followers_count: Nombro de sekvatoj
-    lock_link: Ŝlosi via konton
-    purge: Forigi de sakvantoj
+    explanation_html: Se vi volas esti certa pri la privateco de viaj mesaĝoj, vi bezonas esti atenta pri tiuj, kiuj sekvas vin. <strong>Viaj privataj mesaĝoj estas liveritaj al ĉiuj nodoj, kie vi havas sekvantojn</strong>. Eble vi ŝatus kontroli ilin, kaj forigi la sekvantojn de la nodoj, kie vi ne certas ĉu via privateco estos respektita de la tiea teamo aŭ programo.
+    followers_count: Nombro de sekvantoj
+    lock_link: Ŝlosu vian konton
+    purge: Forigi el la sekvantoj
+    success:
+      one: Forigado de sekvantoj el iu domajno...
+      other: Forigado de sekvantoj el %{count} domajnoj...
+    true_privacy_html: Bonvolu atenti, ke <strong>vera privateco povas esti atingita nur per ĉifrado de komenco al fino</strong>.
+    unlocked_warning_html: Iu ajn povas eksekvi vin por tuj vidi viajn privatajn mesaĝojn. %{lock_link} por povi akcepti kaj rifuzi petojn de sekvado.
+    unlocked_warning_title: Via konto ne estas ŝlosita
   generic:
-    changes_saved_msg: Ŝanĝoj senprobleme konservitaj!
+    changes_saved_msg: Ŝanĝoj sukcese konservitaj!
     powered_by: povigita de %{link}
-    save_changes: Konservi la ŝanĝojn
+    save_changes: Konservi ŝanĝojn
     validation_errors:
-      one: Io ne okazis senprobleme! Bonvolu konsulti la suban erar-raporton.
-      other: Io ne okazis senprobleme! Bonvolu konsulti la subajn %{count} erar-raportojn.
+      one: Io mise okazis! Bonvolu konsulti la suban erar-raporton
+      other: Io mise okazis! Bonvolu konsulti la subajn %{count} erar-raportojn
   imports:
-    preface: Vi povas alporti kelkajn datumojn, kiel listojn de ĉiuj homoj kiujn vi sekvas aŭ blokas, al via konto de ĉi tiu aperaĵo, per dosiero elportita de alia aperaĵo.
-    success: Viaj datumoj estis senprobleme alportitaj kaj estos traktitaj kiel planite.
+    preface: Vi povas importi datumojn, kiujn vi eksportis el alia nodo, kiel liston de homoj, kiujn vi sekvas aŭ blokas.
+    success: Viaj datumoj estis sukcese alŝutitaj kaj estos traktitaj kiel planite
     types:
       blocking: Listo de blokitoj
       following: Listo de sekvatoj
-      muting: Listo de silentoj
-    upload: Alporti
-  landing_strip_html: "<strong>%{name}</strong> estas uzanto en %{link_to_root_path}. Vi povas sekvi tiun aŭ interagi kun tiu, se vi havas konton ie ajn en la Fediverse."
-  landing_strip_signup_html: Se vi ne havas, vi povas <a href="%{sign_up_path}">membriĝi ĉi tie.</a>.
+      muting: Listo de silentigitoj
+    upload: Alŝuti
+  in_memoriam_html: Memore
+  invites:
+    delete: Malaktivigi
+    expired: Eksvalida
+    expires_in:
+      '1800': 30 minutoj
+      '21600': 6 horoj
+      '3600': 1 horo
+      '43200': 12 horoj
+      '86400': 1 tago
+    expires_in_prompt: Neniam
+    generate: Krei
+    max_uses:
+      one: 1 uzo
+      other: "%{count} uzoj"
+    max_uses_prompt: Neniu limo
+    prompt: Krei kaj diskonigi ligilojn al aliaj por doni aliron al ĉi tiu nodo
+    table:
+      expires_at: Eksvalidiĝas
+      uses: Uzoj
+    title: Inviti homojn
+  landing_strip_html: "<strong>%{name}</strong> estas uzanto en %{link_to_root_path}. Vi povas sekvi tiun aŭ interagi kun tiu, se vi havas konton ie ajn en la fediverse."
+  landing_strip_signup_html: Se vi ne havas, vi povas <a href="%{sign_up_path}">registriĝi ĉi tie</a>.
+  lists:
+    errors:
+      limit: Vi atingis la maksimuman kvanton de listoj
   media_attachments:
     validations:
-      images_and_video: Vi ne povas alligi video al statuson kiu jam havas bilojn
-      too_many: Vi ne povas alligi pli ol 4 dosieroj
+      images_and_video: Aldoni videon al mesaĝo, kiu jam havas bildojn ne eblas
+      too_many: Aldoni pli ol 4 dosierojn ne eblas
+  migrations:
+    acct: uzantnomo@domajno de la nova konto
+    currently_redirecting: 'Via profilo alidirektos al:'
+    proceed: Konservi
+    updated_msg: Via agordo pri konta migrado estis sukcese ĝisdatigita!
+  moderation:
+    title: Kontrolado
   notification_mailer:
     digest:
-      body: 'Jen eta resumo de tio, kio okazis en %{instance}, ekde kiam vi laste vizitis en %{since}:'
+      action: Vidi ĉiujn sciigojn
+      body: Jen eta resumo de la mesaĝoj, kiujn vi mistrafis ekde via lasta vizito en %{since}
       mention: "%{name} menciis vin en:"
       new_followers_summary:
-        one: Vi ekhavis novan sekvanton! Jej!
-        other: Vi ekhavis %{count} novajn sekvantojn! Mirinde!
+        one: Ankaŭ, vi ekhavis novan sekvanton en via foresto! Jej!
+        other: Ankaŭ, vi ekhavis %{count} novajn sekvantojn en via foresto! Mirinde!
       subject:
         one: "1 nova sciigo ekde via lasta vizito \U0001F418"
         other: "%{count} novaj sciigoj ekde via lasta vizito \U0001F418"
+      title: En via foresto…
     favourite:
-      body: "%{name} favoris vian mesaĝon:"
-      subject: "%{name} favoris vian mesaĝon"
+      body: "%{name} stelumis vian mesaĝon:"
+      subject: "%{name} stelumis vian mesaĝon"
+      title: Nova stelumo
     follow:
-      body: "%{name} eksekvis vin:"
+      body: "%{name} eksekvis vin!"
       subject: "%{name} eksekvis vin"
+      title: Nova sekvanto
     follow_request:
-      body: "%{name} petis sekvi vin:"
+      action: Administri petojn de sekvado
+      body: "%{name} petis sekvi vin"
       subject: "%{name} petis sekvi vin"
+      title: Nova peto de sekvado
     mention:
+      action: Respondi
       body: "%{name} menciis vin en:"
       subject: "%{name} menciis vin"
+      title: Nova mencio
     reblog:
       body: "%{name} diskonigis vian mesaĝon:"
       subject: "%{name} diskonigis vian mesaĝon"
+      title: Nova diskonigo
   number:
     human:
       decimal_units:
         format: "%n%u"
         units:
-          billion: B
-          million: M
-          quadrillion: Q
-          thousand: K
-          trillion: T
-          unit: ''
+          billion: Md
+          million: Mn
+          quadrillion: Dd
+          thousand: m
+          trillion: Dn
+          unit: " "
   pagination:
+    newer: Pli nova
     next: Sekva
-    prev: Malsekva
+    older: Malpli nova
+    prev: Antaŭa
+    truncate: "&hellip;"
   preferences:
     languages: Lingvoj
-    other: Aliaj
-    publishing: Publikigi
-    web: Ret
+    other: Aliaj aferoj
+    publishing: Publikado
+    web: Reto
   push_notifications:
     favourite:
-      title: "%{name} preferitis via statuso"
+      title: "%{name} stelumis vian mesaĝon"
     follow:
-      title: "%{name} estas sekvantas vin"
+      title: "%{name} eksekvis vin"
     group:
       title: "%{count} sciigoj"
     mention:
-      action_boost: Akceli
-      action_expand: Pli
-      action_favourite: Preferi
-      title: "%{name} menciitis vin"
+      action_boost: Diskonigi
+      action_expand: Montri pli
+      action_favourite: Stelumi
+      title: "%{name} menciis vin"
     reblog:
-      title: "%{name} akcelis via statuson"
+      title: "%{name} diskonigis vian mesaĝon"
   remote_follow:
-    acct: Enmetu vian uzantnomo@aperaĵo de kie vi volas sekvi tiun uzanton
-    missing_resource: La URL de plusendado ne povis esti trovita
-    proceed: Daŭrigi por plusendi
+    acct: Enmetu vian uzantnomo@domajno de kie vi volas sekvi
+    missing_resource: La URL de plusendado ne estis trovita
+    proceed: Daŭrigi por eksekvi
     prompt: 'Vi eksekvos:'
   sessions:
-    activity: Lasta Aktiveco
+    activity: Lasta ago
     browser: Retumilo
-    current_session: Aktuala sesio
-    description: "%{browser} sur %{platform}"
-    explanation: Ĉi tiuj estas la retumiloj nun ensalutinda en via Mastodon konton.
+    browsers:
+      alipay: Alipay
+      blackberry: Blackberry
+      chrome: Chrome
+      edge: Microsoft Edge
+      electron: Electron
+      firefox: Firefox
+      generic: Nekonata retumilo
+      ie: Internet Explorer
+      micro_messenger: MicroMessenger
+      nokia: Nokia S40 Ovi Browser
+      opera: Opera
+      otter: Otter
+      phantom_js: PhantomJS
+      qq: QQ Browser
+      safari: Safari
+      uc_browser: UCBrowser
+      weibo: Weibo
+    current_session: Nuna seanco
+    description: "%{browser} en %{platform}"
+    explanation: Ĉi tiuj estas la retumiloj nun ensalutintaj al via Mastodon-konto.
     ip: IP
-    revoke: Revoki
-    revoke_success: La sesio estas revokis
-    title: Sesioj
+    platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: Blackberry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: Linux
+      mac: Mac
+      other: nekonata platformo
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
+    revoke: Malvalidigi
+    revoke_success: Seanco sukcese malvalidigita
+    title: Seancoj
   settings:
     authorized_apps: Rajtigitaj aplikaĵoj
     back: Reveni al Mastodon
-    delete: Konto forigo
-    development: Evoluno
-    edit_profile: Redakti la profilon
-    export: Elporti datumojn
-    followers: Rajtigis sekvantoj
-    import: Alporti
-    notifications: Avizoj
+    delete: Konta forigo
+    development: Evoluigado
+    edit_profile: Redakti profilon
+    export: Eksporti datumojn
+    followers: Rajtigitaj sekvantoj
+    import: Importi
+    migrate: Konta migrado
+    notifications: Sciigoj
     preferences: Preferoj
     settings: Agordoj
     two_factor_authentication: Dufaktora aŭtentigo
-    your_apps: Via aplikaĵoj
+    your_apps: Viaj aplikaĵoj
   statuses:
     open_in_web: Malfermi retumile
-    over_character_limit: limo de %{max} signoj trapasita
+    over_character_limit: limo de %{max} signoj transpasita
     pin_errors:
-      limit: Tro multaj tootoj fiksis
-      ownership: Aliaja tooto ne povas esti fiksis
-      private: Nepublika tooto ne povas esti fixis
-      reblog: Diskonigo ne povas esti fiksis
+      limit: Vi jam atingis la maksimuman nombron de alpinglitaj mesaĝoj
+      ownership: Mesaĝo de iu alia ne povas esti alpinglita
+      private: Mesaĝo nepublika ne povas esti alpinglita
+      reblog: Diskonigo ne povas esti alpinglita
     show_more: Montri pli
+    title: '%{name}: "%{quote}"'
     visibilities:
       private: Montri nur al sekvantoj
       private_long: Montri nur al sekvantoj
       public: Publika
       public_long: Ĉiuj povas vidi
-      unlisted: Publika, sed ne aperos en publikaj tempolinioj
-      unlisted_long: Publika, sed ne aperos en publikaj tempolinioj
+      unlisted: Nelistigita
+      unlisted_long: Ĉiuj povas vidi, sed nelistigita en publikaj tempolinioj
   stream_entries:
     click_to_show: Alklaki por montri
-    pinned: Fiksis
-    reblogged: diskonigis
+    pinned: Alpinglita
+    reblogged: diskonigita
     sensitive_content: Tikla enhavo
   terms:
-    title: "%{instance} Reguloj de servo kaj Politikaj pri privatecoj"
+    body_html: |
+      <h2>Privateca politiko</h2>
+
+      <h3 id="collect">Kiujn informojn ni kolektas?</h3>
+
+      <p>Ni kolektas informojn de vi, kiam vi registriĝas en nia retejo aŭ partoprenas en la forumo per legado, skribado, kaj traktado de la enhavo diskonigita ĉi tie.</p>
+
+      <p>En registriĝo, ni povas peti al vi vian nomon kaj retadreson. Vi tamen povas viziti nian retejon sen registriĝo. Via retadreso estos validigita per retmesaĝo, kiu enhavos unikan ligilon. Se tiu ligilo estas vizitita, ni scios ke vi regas la retadreson.</p>
+
+      <p>Post registriĝo, ni registras la IP-adreson de tiu, kiu kreas mesaĝon. Ni ankaŭ povas konservi servilan historion, en kiu troviĝas la IP-adreso de ĉiu peto al nia servilo.</p>
+
+      <h3 id="use">Por kio ni uzas viajn informojn?</h3>
+
+      <p>Ajna informo, kiun ni kolektas povas esti uzata por unu el tiuj celoj:</p>
+
+      <ul>
+        <li>Proprigi vian sperton &mdash; viaj informoj helpas nin pli bone respondi al viaj propraj bezonoj.</li>
+        <li>Plibonigi nian retejon &mdash; ni daŭre klopodas por plibonigi nian retejon uzante la informojn kaj komentojn, kiujn ni ricevas de vi.</li>
+        <li>Plibonigi nian helpon al klientoj &mdash; viaj informoj helpas nin pli bone respondi al klientaj petoj kaj al subtenaj bezonoj.</li>
+        <li>Sendi periodajn retmesaĝojn &mdash; La retadreso, kiun vi donas al ni, povas esti uzata por sendi al vi informojn kaj sciigojn, kiujn vi volas ricevi pri ŝanĝoj rilate al apartaj temoj, aŭ responde al via uzantnomo, al petoj kaj al demandoj.</li>
+      </ul>
+
+      <h3 id="protect">Kiel ni protektas viajn informojn?</h3>
+
+      <p>Ni realigis diversajn sekurigajn procedojn por konservi la sekurecon de viaj personaj informoj kiam vi enmetas, sendas, aŭ aliras viajn personajn informojn.</p>
+
+      <h3 id="data-retention">Kio estas nia politiko pri konservado de datumoj?</h3>
+
+      <p>Ni honeste klopodas:</p>
+
+      <ul>
+        <li>Ne konservi servilan historion, kiu enhavas la IP-adresojn de ĉiuj petoj, dum pli ol 90 tagoj.</li>
+        <li>Ne konservi la IP-adresojn de registritaj uzantoj kaj de iliaj mesaĝoj dum pli ol 5 jaroj.</li>
+      </ul>
+
+      <h3 id="cookies">Ĉu ni uzas kuketojn?</h3>
+
+      <p>Jes. Kuketoj estas etaj dosieroj, kiujn retejo aŭ ĝia servo donas al la memoro de via komputilo, per via retumilo (se vi permesas tion). Ĉi tiuj kuketoj ebligas al la retejo rekoni vian retumilon, kaj se vi havas registritan konton, ligas ĝin al via registrita konto.</p>
+
+      <p>Ni uzas kuketojn por kompreni kaj konservi viajn preferojn por postaj vizitoj, kaj kunmeti informojn pri reteja trafiko kaj interago, por ke ni povu doni pli bonan retejan sperton kaj pli bonajn ilojn estonte. Ni povas kontrakti kun eksteraj servoj por helpi nin pli bone kompreni la vizitantojn de la retejo. Ĉi tiuj eksteraj servoj ne rajtas uzi la informojn, kiujn ni kolektis, krom por helpi nin regi kaj plibonigi nian komercon.</p>
+
+      <h3 id="disclose">Ĉu ni disdonas informojn al eksteraj personoj?</h3>
+
+      <p>Ni ne vendas, interŝanĝas aŭ transdonas al eksteraj personoj viajn persone identigeblajn informojn. Ĉi tio ne inkludas la eksterajn servojn, kiujn ni fidas, kiuj helpas nin funkciigi nian retejon, regi nian komercon, aŭ servi vin, kiom longe tiuj personoj konsentas pri la sekura konservado de ĉi tiuj informoj. Ni ankaŭ povas disdoni viajn informojn, kiam ni pensas ke tio estas nepra por respekti leĝojn, por respektigi la politikojn de nia retejo, aŭ por protekti la rajtojn, posedaĵojn, kaj sekurecon de ni kaj de aliaj. Tamen, informoj de vizitantoj, kiuj ne identigas personojn, povas esti donitaj al eksteraj personoj por merkatado, reklamado, aŭ aliaj uzoj.</p>
+
+      <h3 id="third-party">Eksteraj ligiloj</h3>
+
+      <p>Foje, laŭ nia elekto, ni povas enmeti aŭ oferti eksterajn produktojn aŭ servojn en nia retejo. Ĉi tiuj eksteraj retejoj havas apartajn kaj sendependajn privatecajn politikojn. Tial, ni havas nek responsojn nek devigojn rilate al la enhavoj kaj agadoj de ĉi tiuj ligitaj retejoj. Tamen, ni celas protekti tiujn, kiuj uzas nian retejon, kaj bonvenigas ajnan komenton pri ĉi tiuj retejoj.</p>
+
+      <h3 id="coppa">Children's Online Privacy Protection Act Compliance</h3>
+
+      <p>Niaj retejo, produktoj kaj servoj estas por tiuj, kiuj havas almenaŭ 13 jarojn. Se ĉi tiu servilo estas en Usono, kaj vi havas malpli ol 13 jarojn, pro la postuloj de COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) ne uzu ĉi tiun retejon.</p>
+
+      <h3 id="online">Privateca politiko nur rete</h3>
+
+      <p>Ĉi tiu privateca politiko validas nur por informoj kolektitaj per nia retejo kaj ne por informoj kolektitaj eksterrete.</p>
+
+      <h3 id="consent">Via konsento</h3>
+
+      <p>Per uzado de nia retejo, vi konsentas kun nia reta privateca politiko.</p>
+
+      <h3 id="changes">Ŝanĝoj al nia privateca politiko</h3>
+
+      <p>Se ni decidas ŝanĝi nian privatecan politikon, ni afiŝos tiujn ŝanĝojn en ĉi tiu paĝo.</p>
+
+      <p>Ĉi tiu dokumento estas laŭ permeso CC-BY-SA. Ĝi estis laste ĝisdatigita je 2018-02-27.</p>
+
+      <p>Originale adaptita el la <a href="https://github.com/discourse/discourse">privateca politiko de Discourse</a>.</p>
+    title: Uzkondiĉoj kaj privateca politiko de %{instance}
+  themes:
+    default: Mastodon
   time:
     formats:
-      default: "%b %d, %Y, %H:%M"
+      default: "%Y-%m-%d %H:%M"
   two_factor_authentication:
-    description_html: Se vi ebligas <strong>dufaktoran aŭtentigon</strong>, vi bezonos vian poŝtelefonon por ensaluti, ĉar ĝi kreos nombrojn, kiujn vi devos entajpi.
+    code_hint: Enmetu la kodon kreitan de via aŭtentiga aplikaĵo por konfirmi
+    description_html: Se vi ebligas <strong>dufaktoran aŭtentigon</strong>, vi bezonos vian poŝtelefonon por ensaluti, ĉar ĝi kreos nombrojn, kiujn vi devos enmeti.
     disable: Malebligi
     enable: Ebligi
-    instructions_html: "<strong>Skanu tiun QR-kodon per Google Authenticator aŭ per simila aplikaĵo de via poŝtelefono</strong>. De tiam, la aplikaĵo kreos nombrojn, kiujn vi devos entajpi."
+    enabled: Dufaktora aŭtentigo ebligita
+    enabled_success: Dufaktora aŭtentigo sukcese ebligita
+    generate_recovery_codes: Krei realirajn kodojn
+    instructions_html: "<strong>Skanu ĉi tiun QR-kodon per Google Authenticator aŭ per simila aplikaĵo en via poŝtelefono</strong>. De tiam, la aplikaĵo kreos nombrojn, kiujn vi devos enmeti."
+    lost_recovery_codes: Realiraj kodoj permesas rehavi aliron al via konto se vi perdis vian telefonon. Se vi perdis viajn realirajn kodojn, vi povas rekrei ilin ĉi tie. Viaj malnovaj realiraj kodoj iĝos eksvalidaj.
+    manual_instructions: 'Se vi ne povas skani la QR-kodon kaj bezonas enmeti ĝin mane, jen la tut-teksta sekreto:'
+    recovery_codes: Realiraj kodoj
+    recovery_codes_regenerated: Realiraj kodoj sukcese rekreitaj
+    recovery_instructions_html: Se vi perdas aliron al via telefono, vi povas uzi unu el la subaj realiraj kodoj por rehavi aliron al via konto. <strong>Konservu realirajn kodojn sekure</strong>. Ekzemple, vi povas printi ilin kaj konservi ilin kun aliaj gravaj dokumentoj.
+    setup: Agordi
+    wrong_code: La enmetita kodo estis nevalida! Ĉu la servila tempo kaj la aparata tempo ĝustas?
+  user_mailer:
+    backup_ready:
+      explanation: Vi petis kompletan arkivon de via Mastodon-konto. Ĝi nun pretas por elŝutado!
+      subject: Via arkivo estas preta por elŝutado
+      title: Arkiva elŝuto
+    welcome:
+      edit_profile_action: Agordi profilon
+      edit_profile_step: Vi povas proprigi vian profilon per alŝuto de profilbildo, fonbildo, ŝanĝo de via afiŝita nomo kaj pli. Se vi ŝatus kontroli novajn sekvantojn antaŭ ol ili rajtas sekvi vin, vi povas ŝlosi vian konton.
+      explanation: Jen kelkaj konsiloj por helpi vin komenci
+      final_action: Ekmesaĝi
+      final_step: 'Ekmesaĝu! Eĉ sen sekvantoj, viaj publikaj mesaĝoj povas esti vidataj de aliaj, ekzemple en la loka tempolinio kaj en la kradvortoj. Eble vi ŝatus prezenti vin per la kradvorto #introductions.'
+      full_handle: Via kompleta uzantnomo
+      full_handle_hint: Jen kion vi dirus al viaj amikoj, por ke ili mesaĝu aŭ sekvu vin de alia nodo.
+      review_preferences_action: Ŝanĝi preferojn
+      review_preferences_step: Estu certa ke vi agordis viajn preferojn, kiel kiujn retmesaĝojn vi ŝatus ricevi, aŭ kiun dekomencan privatecan nivelon vi ŝatus ke viaj mesaĝoj havu. Se tio ne ĝenas vin, vi povas ebligi aŭtomatan ekigon de GIF-oj.
+      subject: Bonvenon en Mastodon
+      tip_bridge_html: Se vi venas de Twitter, vi povas trovi viajn amikojn en Mastodon per uzo de la <a href="%{bridge_url}">ponta aplikaĵo</a>. Tamen, tio funkcias nur se ankaŭ ili uzis la pontan aplikaĵon!
+      tip_federated_timeline: La fratara tempolinio estas antaŭvido de la reto de Mastodon. Sed ĝi enhavas nur homojn, kiuj estas sekvataj de aliaj homoj de via nodo, do ĝi ne estas kompleta.
+      tip_following: Vi dekomence sekvas la administrantojn de via servilo. Por trovi pli da interesaj homoj, rigardu la lokan kaj frataran tempoliniojn.
+      tip_local_timeline: La loka tempolinio estas antaŭvido de la homoj en %{instance}. Ĉi tiuj estas viaj apudaj najbaroj!
+      tip_mobile_webapp: Se via telefona retumilo proponas al vi aldoni Mastodon al via hejma ekrano, vi povas ricevi puŝsciigojn. Tio multmaniere funkcias kiel operaciuma aplikaĵo!
+      tips: Konsiloj
+      title: Bonvenon, %{name}!
   users:
-    invalid_email: La retpoŝt-adreso ne estas valida
-    invalid_otp_token: La dufaktora aŭtentigila kodo ne estas valida
+    invalid_email: La retadreso estas nevalida
+    invalid_otp_token: Nevalida kodo de dufaktora aŭtentigo
+    seamless_external_login: Vi estas ensalutinta per ekstera servo, do pasvortaj kaj retadresaj agordoj ne estas disponeblaj.
+    signed_in_as: 'Ensalutinta kiel:'
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 7ee2876a9..490edcfee 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -23,7 +23,6 @@ es:
       real_conversation_title: Hecho para verdaderas conversaciones
       within_reach_body: Aplicaciones múltiples para iOS, Android, y otras plataformas gracias a un ecosistema de APIs amigable al desarrollador para permitirte estar con tus amigos donde sea.
       within_reach_title: Siempre al alcance
-    find_another_instance: Busca otra instancia
     generic_description: "%{domain} es un servidor en la red"
     hosted_on: Mastodon hosteado en %{domain}
     learn_more: Aprende más
@@ -116,6 +115,7 @@ es:
       roles:
         admin: Administrador
         moderator: Moderador
+        staff: Personal
         user: Usuario
       salmon_url: URL de salmón
       search: Buscar
@@ -160,6 +160,7 @@ es:
         update_status: "%{name} actualizó el estado de %{target}"
       title: Log de auditoría
     custom_emojis:
+      by_domain: Dominio
       copied_msg: Copia local del emoji creada con éxito
       copy: Copiar
       copy_failed_msg: No se pudo realizar una copia local de ese emoji
@@ -263,12 +264,18 @@ es:
       unresolved: No resuelto
       view: Ver
     settings:
+      activity_api_enabled:
+        desc_html: Conteo de estados publicados localmente, usuarios activos, y nuevos registros en  periodos semanales
+        title: Publicar estadísticas locales acerca de actividad de usuario
       bootstrap_timeline_accounts:
         desc_html: Separa con comas los nombres de usuario. Solo funcionará para cuentas locales desbloqueadas. Si se deja vacío, se tomará como valor por defecto a todos los administradores locales.
         title: Seguimientos predeterminados para usuarios nuevos
       contact_information:
         email: Correo de trabajo
         username: Nombre de usuario
+      peers_api_enabled:
+        desc_html: Nombres de dominio que esta instancia ha encontrado en el fediverso
+        title: Publicar lista de instancias descubiertas
       registrations:
         closed_message:
           desc_html: Se muestra en la portada cuando los registros están cerrados. Puedes usar tags HTML
@@ -330,9 +337,12 @@ es:
       body: "%{reporter} ha reportado a %{target}"
       subject: Nuevo reporte para la %{instance} (#%{id})
   application_mailer:
+    notification_preferences: Cambiar preferencias de correo electrónico
     salutation: "%{name},"
     settings: 'Cambiar preferencias de correo: %{link}'
     view: 'Vista:'
+    view_profile: Ver perfil
+    view_status: Ver estado
   applications:
     created: Aplicación creada exitosamente
     destroyed: Apicación eliminada exitosamente
@@ -342,8 +352,7 @@ es:
     warning: Ten mucho cuidado con estos datos. ¡No los compartas con nadie!
     your_token: Tu token de acceso
   auth:
-    agreement_html: Al registrarte aceptas <a href="%{rules_path}">nuestros y términos y condiciones del servicio</a> y <a href="%{terms_path}">nuestras políticas de privacidda</a>.
-    change_password: Cambiar contraseña
+    agreement_html: Al registrarte, acepta seguir <a href="%{rules_path}">las reglas de la instancia</a> y <a href="%{terms_path}">nuestros términos de servicio</a>.
     delete_account: Borrar cuenta
     delete_account_html: Si desea eliminar su cuenta, puede <a href="%{path}">proceder aquí</a>. Será pedido de una confirmación.
     didnt_get_confirmation: "¿No recibió el correo de confirmación?"
@@ -356,6 +365,7 @@ es:
     register: Registrarse
     resend_confirmation: Volver a enviar el correo de confirmación
     reset_password: Restablecer contraseña
+    security: Cambiar contraseña
     set_new_password: Establecer nueva contraseña
   authorize_follow:
     error: Desafortunadamente, ha ocurrido un error buscando la cuenta remota
@@ -457,6 +467,9 @@ es:
     title: Invitar a gente
   landing_strip_html: "<strong>%{name}</strong> es un usuario en %{link_to_root_path}. Puedes seguirlo(a) o interactuar con el o ella si tienes una cuenta en cualquier parte del fediverse."
   landing_strip_signup_html: Si no tienes una, puedes <a href="%{sign_up_path}">registrarte aquí</a>.
+  lists:
+    errors:
+      limit: Has alcanzado la cantidad máxima de listas
   media_attachments:
     validations:
       images_and_video: No se puede adjuntar un video a un estado que ya contenga imágenes
@@ -470,29 +483,38 @@ es:
     title: Moderación
   notification_mailer:
     digest:
-      body: 'Un resumen de lo que te perdiste en %{instance} desde tu última visita el %{since}:'
+      action: Ver todas las notificaciones
+      body: Un resumen de los mensajes que perdiste en desde tu última visita, el %{since}
       mention: "%{name} te ha mencionado en:"
       new_followers_summary:
-        one: "¡Hurra! ¡Tienes un nuevo seguidor!"
-        other: "¡Genial! ¡Tienes %{count} nuevos seguidores!"
+        one: "¡Ademas, has adquirido un nuevo seguidor mientras no estabas! ¡Hurra!"
+        other: "¡Ademas, has adquirido %{count} nuevos seguidores mientras no estabas! ¡Genial!"
       subject:
         one: "1 nueva notificación desde tu última visita \U0001F418"
         other: "%{count} nuevas notificaciones desde tu última visita \U0001F418"
+      title: En tu ausencia…
     favourite:
       body: 'Tu estado fue marcado como favorito por %{name}:'
       subject: "%{name} marcó como favorito tu estado"
+      title: Nuevo favorito
     follow:
       body: "¡%{name} te está siguiendo!"
       subject: "%{name} te está siguiendo"
+      title: Nuevo seguidor
     follow_request:
+      action: Administrar solicitudes para seguir
       body: "%{name} ha solicitado seguirte"
       subject: 'Seguidor pendiente: %{name}'
+      title: Nueva solicitud para seguir
     mention:
+      action: Responder
       body: 'Fuiste mencionado por %{name} en:'
       subject: Fuiste mencionado por %{name}
+      title: Nueva mención
     reblog:
       body: "%{name} ha retooteado tu estado:"
       subject: "%{name} ha retooteado tu estado"
+      title: Nueva difusión
   number:
     human:
       decimal_units:
@@ -528,7 +550,7 @@ es:
     reblog:
       title: "%{name} boosteó tu estado"
   remote_follow:
-    acct: Ingesa el usuario@dominio de la persona que quieres seguir
+    acct: Ingesa tu usuario@dominio desde el que quieres seguir
     missing_resource: No se pudo encontrar la URL de redirección requerida para tu cuenta
     proceed: Proceder a seguir
     prompt: 'Vas a seguir a:'
@@ -540,12 +562,14 @@ es:
       blackberry: Blackberry
       chrome: Chrome
       edge: Microsoft Edge
+      electron: Electron
       firefox: Firefox
       generic: Desconocido
       ie: Internet Explorer
       micro_messenger: MicroMessenger
       nokia: Nokia S40 Ovi Browser
       opera: Opera
+      otter: Otter
       phantom_js: PhantomJS
       qq: QQ Browser
       safari: Safari
@@ -590,11 +614,12 @@ es:
     open_in_web: Abrir en web
     over_character_limit: Límite de caracteres de %{max} superado
     pin_errors:
-      limit: Demasiados toots fijados
+      limit: Ya has fijado el número máximo de publicaciones
       ownership: El toot de alguien más no puede fijarse
       private: Los toots no-públicos no pueden fijarse
       reblog: Un boost no puede fijarse
     show_more: Mostrar más
+    title: '%{name}: "%{quote}"'
     visibilities:
       private: Sólo mostrar a seguidores
       private_long: Solo mostrar a tus seguidores
@@ -696,6 +721,23 @@ es:
     recovery_instructions_html: Si pierdes acceso a tu teléfono, puedes usar uno de los siguientes códigos de recuperación para obtener acceso a tu cuenta. <strong>Mantenlos a salvo</strong>. Por ejemplo, puedes imprimirlos y guardarlos con otros documentos importantes.
     setup: Configurar
     wrong_code: "¡El código ingresado es inválido! ¿El dispositivo y tiempo del servidor están correctos?"
+  user_mailer:
+    welcome:
+      edit_profile_action: Configurar el perfil
+      explanation: Aquí hay algunos consejos para iniciar
+      final_action: Empezar a publicar
+      final_step: '¡Empieza a publicar! Incluso sin seguidores, tus mensajes públicos pueden ser vistos por otros, por ejemplo en la linea de tiempo local y con "hashtags". Podrías querer introducirte con el "hashtag" #introductions.'
+      full_handle: Su sobrenombre completo
+      full_handle_hint: Esto es lo que le dirías a tus amigos para que ellos puedan enviarte mensajes o seguirte desde otra instancia.
+      review_preferences_action: Cambiar preferencias
+      review_preferences_step: Asegurate de poner tus preferencias, como que correos te gustaría recibir, o que nivel de privacidad te gustaría que tus publicaciones tengan por defecto. Si no tienes mareos, podrías elegir habilitar la reproducción automática de "GIFs".
+      subject: Bienvenido a Mastodon
+      tip_bridge_html: Si esta viniendo desde Twitter, puedes encontrar a tus amigos en Mastodon usando la <a href="%{bridge_url}">aplicación puente</a>. Aunque solo funciona si ellos también usaron la aplicación puente!
+      tip_following: Sigues a tus administradores de servidor por defecto. Para encontrar más gente interesante, revisa las lineas de tiempo local y federada.
+      tip_local_timeline: La linea de tiempo local is una vista de la gente en %{instance}. Estos son tus vecinos inmediatos!
+      tip_mobile_webapp: Si el navegador de tu dispositivo móvil ofrece agregar Mastodon a tu página de inicio, puedes recibir notificaciones. Actúa como una aplicación nativa en muchas formas!
+      tips: Tips
+      title: Bienvenido a bordo, %{name}!
   users:
     invalid_email: La dirección de correo es incorrecta
     invalid_otp_token: Código de dos factores incorrecto
diff --git a/config/locales/fa.yml b/config/locales/fa.yml
index c498c592c..86756c01b 100644
--- a/config/locales/fa.yml
+++ b/config/locales/fa.yml
@@ -23,7 +23,6 @@ fa:
       real_conversation_title: برای گفتگوهای واقعی
       within_reach_body: اپ‌های متنوع برای iOS، اندروید، و سیستم‌های دیگر به خاطر وجود یک اکوسیستم API دوستانه برای برنامه‌نویسان. از همه جا با دوستان خود ارتباط داشته باشید.
       within_reach_title: همیشه در دسترس
-    find_another_instance: یافتن سرورهای دیگر
     generic_description: "%{domain} یک سرور روی شبکه است"
     hosted_on: ماستدون، میزبانی‌شده روی %{domain}
     learn_more: بیشتر بدانید
@@ -345,7 +344,6 @@ fa:
     your_token: کد دسترسی شما
   auth:
     agreement_html: پیش از عضو شدن باید <a href="%{rules_path}">قوانین این سرور</a> و <a href="%{terms_path}">شرایط استفادهٔ</a> ما را بپذیرید.
-    change_password: امنیت
     delete_account: پاک‌کردن حساب
     delete_account_html: اگر می‌خواهید حساب خود را پاک کنید، از <a href="%{path}">این‌جا</a> پیش بروید. از شما درخواست تأیید خواهد شد.
     didnt_get_confirmation: راهنمایی برای تأیید را دریافت نکردید؟
@@ -358,6 +356,7 @@ fa:
     register: عضو شوید
     resend_confirmation: راهنمایی برای تأیید را دوباره بفرست
     reset_password: بازنشانی رمز
+    security: امنیت
     set_new_password: تعیین رمز تازه
   authorize_follow:
     error: متأسفانه حین یافتن آن حساب خطایی رخ داد
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index f2ee28ba0..71e019e54 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -3,16 +3,32 @@ fi:
   about:
     about_mastodon_html: Mastodon on <em>vapaa, avoimeen lähdekoodiin perustuva</em> sosiaalinen verkosto. <em>Hajautettu</em> vaihtoehto kaupallisille alustoille, se välttää eiskit yhden yrityksen monopolisoinnin sinun viestinnässäsi. Valitse palvelin mihin luotat &mdash; minkä tahansa valitset, voit vuorovaikuttaa muiden kanssa. Kuka tahansa voi luoda Mastodon palvelimen ja ottaa osaa <em>sosiaaliseen verkkoon</em> saumattomasti.
     about_this: Tietoja tästä palvelimesta
+    closed_registrations: Rekisteröityminen tässä instanssissa on juuri nyt suljettu. Mutta! Voit yhdistää täysin samaan, yhteiseen verkostoon rekisteröitymällä jossain toisessa instanssissa.
     contact: Ota yhteyttä
+    contact_missing: Ei asetettu
+    contact_unavailable: Ei saatavilla
     description_headline: Mikä on %{domain}?
     domain_count_after: muuhun palvelimeen
     domain_count_before: Yhdistyneenä
+    extended_description_html: |
+      <h3>Hyvä paikka säännöille</h3>
+      <p>Pidennettyä kuvausta ei ole vielä asetettu.</p>
+    features:
+      humane_approach_body: Muiden verkostojen virheistä oppien, Mastodon pyrkii tekemään eettisiä valintoja suunnittelussa taistellakseen sosiaalisen median väärinkäyttöä vastaan.
+      humane_approach_title: Humaanimpi lähestymistapa
+      not_a_product_body: Mastodon ei ole kaupallinen verkosto. Ei mainoksia, ei tiedonlouhintaa, ei suljettuja sisäpiirejä. Mastodonissa ei ole keskitettyä auktoriteettiä.
+      not_a_product_title: Olet henkilö, et tuote
+      real_conversation_title: Rakennettu oikealle keskustelulle
+      within_reach_body: Kehittäjäystävällisen rajapintaekosysteemin ansiosta useita appeja Androidille, iOS:lle ja muille alustoille, jotka mahdollistavat yhteydenpidon ystäviesi kanssa missä vain.
+      within_reach_title: Aina lähellä
+    learn_more: Lisätietoja
     other_instances: Muut palvelimet
     source_code: Lähdekoodi
     status_count_after: statusta
     status_count_before: Ovat luoneet
     user_count_after: käyttäjälle
     user_count_before: Koti
+    what_is_mastodon: Mikä on Mastodon?
   accounts:
     follow: Seuraa
     followers: Seuraajat
@@ -22,14 +38,35 @@ fi:
     people_who_follow: Henkilöt jotka seuraa %{name}
     posts: Postaukset
     remote_follow: Etäseuranta
+    reserved_username: Käyttäjänimi on varattu
+    roles:
+      admin: Ylläpitäjä
     unfollow: Lopeta seuraaminen
+  admin:
+    account_moderation_notes:
+      account: Moderaattori
+      create: Luo
+      created_at: Päiväys
+      created_msg: Moderointimerkintä luotu onnistuneesti!
+      delete: Poista
+      destroyed_msg: Moderointimerkintä tuhottu onnistuneesti!
+    accounts:
+      are_you_sure: Oletko varma?
+      confirm: Hyväksy
+      confirmed: Hyväksytty
+      disable: Poista käytöstä
+      disable_two_factor_authentication: Poista 2FA käytöstä
+      disabled: Poistettu käytöstä
+      edit: Muokkaa
+      email: Sähköposti
+      followers: Seuraajat
+      followers_url: Seuraajat URL
   application_mailer:
     settings: 'Muokkaa sähköpostiasetuksia: %{link}'
     view: 'Katso:'
   applications:
     invalid_url: Annettu URL on väärä
   auth:
-    change_password: Tunnukset
     didnt_get_confirmation: Etkö saanut varmennusohjeita?
     forgot_password: Unohditko salasanasi?
     login: Kirjaudu sisään
@@ -37,6 +74,7 @@ fi:
     register: Rekisteröidy
     resend_confirmation: Lähetä varmennusohjeet uudestaan
     reset_password: Palauta salasana
+    security: Tunnukset
     set_new_password: Aseta uusi salasana
   authorize_follow:
     error: Valitettavasti tapahtui virhe etätilin haussa.
@@ -69,7 +107,7 @@ fi:
       one: Jokin ei ole viellä oikein! Katso virhe alapuolelta.
       other: Jokin ei ole viellä oikein! Katso %{count} virhettä alapuolelta.
   imports:
-    preface: Voit tuoda tiettyä dataa kaikista ihmisistä joita seuraat tai estät tilillesi tälle palvelimelle tiedostoista, jotka on luotu toisella palvelimella
+    preface: Voit tuoda tiettyä dataa kaikista ihmisistä joita seuraat tai estät tilillesi tälle palvelimelle tiedostoista, jotka on luotu toisella palvelimella.
     success: Datasi on onnistuneesti ladattu ja käsitellään pian
     types:
       blocking: Estetyt lista
@@ -146,7 +184,7 @@ fi:
     formats:
       default: "%b %d, %Y, %H:%M"
   two_factor_authentication:
-    description_html: Jos otat käyttöön <strong>kaksivaiheisen tunnistuksen</stron>, kirjautumiseen vaaditaan puhelin, joka voi luoda tokeneita kirjautumista varten.
+    description_html: Jos otat käyttöön <strong>kaksivaiheisen tunnistuksen</strong>, kirjautumiseen vaaditaan puhelin, joka voi luoda tokeneita kirjautumista varten.
     disable: Poista käytöstä
     enable: Ota käyttöön
     instructions_html: "<strong>Skannaa tämä QR-koodi Google Authenticator- tai vastaavaan sovellukseen puhelimellasi</strong>. Tästä hetkestä lähtien ohjelma luo koodin, mikä sinun tarvitsee syöttää sisäänkirjautuessa."
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 09338fdfd..0dc0bc306 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -23,7 +23,6 @@ fr:
       real_conversation_title: Construit pour de vraies conversations
       within_reach_body: Grâce à l’existence d’un environnement API accueillant pour les développeur·se·s, de multiples applications pour iOS, Android et d’autres plateformes vous permettent de rester en contact avec vos ami·e·s où que vous soyez.
       within_reach_title: Toujours à portée de main
-    find_another_instance: Trouver une autre instance
     generic_description: "%{domain} est seulement un serveur du réseau"
     hosted_on: Instance Mastodon hébergée par %{domain}
     learn_more: En savoir plus
@@ -39,7 +38,7 @@ fr:
     followers: Abonné⋅e⋅s
     following: Abonnements
     media: Médias
-    moved_html: "%{name} a déménagé vers %{new_profile_link} :"
+    moved_html: "%{name} a changé de compte pour %{new_profile_link} :"
     nothing_here: Rien à voir ici !
     people_followed_by: Personnes suivies par %{name}
     people_who_follow: Personnes qui suivent %{name}
@@ -116,7 +115,7 @@ fr:
       roles:
         admin: Administrateur
         moderator: Modérateur
-        staff: Personnel
+        staff: Équipe
         user: Utilisateur
       salmon_url: URL Salmon
       search: Rechercher
@@ -265,12 +264,21 @@ fr:
       unresolved: Non résolus
       view: Voir
     settings:
+      activity_api_enabled:
+        desc_html: Nombre de statuts affichés localement, d'utilisateurs actifs et de nouveaux enregistrements dans les registres hebdomadaires
+        title: Publier des statistiques agrégées sur l'activité des utilisateurs
       bootstrap_timeline_accounts:
         desc_html: Séparez les noms d’utilisateur·ice par des virgules. Ne fonctionne qu’avec des comptes locaux et non-verrouillés. Si laissé vide, tous les administrateur⋅ice⋅s locaux sont sélectionné⋅e⋅s.
         title: Abonnements par défaut pour les nouveaux·elles utilisateur·ice·s
       contact_information:
         email: Entrez une adresse courriel publique
         username: Entrez un nom d’utilisateur⋅ice
+      hero:
+        desc_html: Affichée sur la page d'accueil. Au moins 600x100px recommandé. Lorsqu'elle n'est pas définie, se rabat sur la vignette de l'instance
+        title: Image d'en-tête
+      peers_api_enabled:
+        desc_html: Noms des domaines que cette instance a découvert dans le fediverse
+        title: Publier la liste des instances découvertes
       registrations:
         closed_message:
           desc_html: Affiché sur la page d’accueil lorsque les inscriptions sont fermées<br>Vous pouvez utiliser des balises HTML
@@ -284,11 +292,14 @@ fr:
         open:
           desc_html: Autoriser tout le monde à créer un compte
           title: Ouvrir les inscriptions
+      show_known_fediverse_at_about_page:
+        desc_html: Lorsque l'option est activée, les pouets provenant de toutes les instances connues sont affichés dans la prévisualisation. Si non, seuls les pouets locaux sont affichés.
+        title: Afficher le fediverse connu dans la prévisualisation du fil
       show_staff_badge:
         desc_html: Montrer un badge de responsable sur une page utilisateur
         title: Montrer un badge de responsable
       site_description:
-        desc_html: Paragraphe introductif sur la page d'accueil et dans les balises meta. Vous pouvez utiliser des balises HTML, en particulier <code>&lt;a&gt;</code> et <code>&lt;em&gt;</code>.
+        desc_html: Paragraphe introductif sur la page d'accueil et dans les méta-balises. Vous pouvez utiliser des balises HTML, en particulier <code>&lt;a&gt;</code> et <code>&lt;em&gt;</code>.
         title: Description du site
       site_description_extended:
         desc_html: Affichée sur la page d’informations complémentaires du site<br>Vous pouvez utiliser des balises HTML
@@ -332,9 +343,12 @@ fr:
       body: "%{reporter} a signalé %{target}"
       subject: Nouveau signalement sur %{instance} (#%{id})
   application_mailer:
+    notification_preferences: Modifier les préférences de courriel
     salutation: "%{name},"
     settings: 'Changer les préférences courriel : %{link}'
     view: 'Voir :'
+    view_profile: Voir le profil
+    view_status: Afficher le statut
   applications:
     created: Application créée avec succès
     destroyed: Application supprimée avec succès
@@ -345,7 +359,8 @@ fr:
     your_token: Votre jeton d’accès
   auth:
     agreement_html: En vous inscrivant, vous souscrivez <a href="%{rules_path}">aux règles de l’instance</a> et à <a href="%{terms_path}">nos conditions d’utilisation</a>.
-    change_password: Sécurité
+    change_password: Mot de passe
+    confirm_email: Confirmer mon adresse mail
     delete_account: Supprimer le compte
     delete_account_html: Si vous désirez supprimer votre compte, vous pouvez <a href="%{path}">cliquer ici</a>. Il vous sera demandé de confirmer cette action.
     didnt_get_confirmation: Vous n’avez pas reçu les consignes de confirmation ?
@@ -355,9 +370,16 @@ fr:
     logout: Se déconnecter
     migrate_account: Déplacer vers un compte différent
     migrate_account_html: Si vous voulez rediriger ce compte vers un autre, vous pouvez le <a href="%{path}">configurer ici</a>.
+    or: ou
+    or_log_in_with: Ou authentifiez-vous avec
+    providers:
+      cas: CAS
+      saml: SAML
     register: S’inscrire
+    register_elsewhere: S'inscrire sur un autre serveur
     resend_confirmation: Envoyer à nouveau les consignes de confirmation
     reset_password: Réinitialiser le mot de passe
+    security: Sécurité
     set_new_password: Définir le nouveau mot de passe
   authorize_follow:
     error: Malheureusement, il y a eu une erreur en cherchant les détails du compte distant
@@ -404,6 +426,13 @@ fr:
       title: Cette page n’est pas correcte
     noscript_html: Pour utiliser Mastodon, veuillez activer JavaScript. Sinon, essayez l'une des <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">applications natives</a> pour Mastodon pour votre plate-forme.
   exports:
+    archive_takeout:
+      date: Date
+      download: Télécharger votre archive
+      hint_html: Vous pouvez demander une archive de vos  <strong>pouets et médias téléversés</strong>. Les données exportées seront au format ActivityPub, lisible par tout logiciel compatible.
+      in_progress: Élaboration de votre archive....
+      request: Demandez vos archives
+      size: Taille
     blocks: Vous bloquez
     csv: CSV
     follows: Vous suivez
@@ -436,7 +465,7 @@ fr:
       following: Liste d’utilisateur⋅ice⋅s suivi⋅e⋅s
       muting: Liste d’utilisateur⋅ice⋅s que vous masquez
     upload: Importer
-  in_memoriam_html: In Memoriam.
+  in_memoriam_html: En mémoire de.
   invites:
     delete: Désactiver
     expired: Expiré
@@ -475,29 +504,38 @@ fr:
     title: Modération
   notification_mailer:
     digest:
+      action: Voir toutes les notifications
       body: 'Voici ce que vous avez raté sur ${instance} depuis votre dernière visite le %{since} :'
       mention: "%{name} vous a mentionné⋅e dans :"
       new_followers_summary:
         one: Vous avez un⋅e nouvel⋅le abonné⋅e ! Youpi !
-        other: Vous avez %{count} nouveaux⋅elles abonné⋅e⋅s ! Incroyable !
+        other: Vous avez %{count} nouveaux⋅elles abonné⋅e·s ! Incroyable !
       subject:
         one: "Une nouvelle notification depuis votre dernière visite \U0001F418"
         other: "%{count} nouvelles notifications depuis votre dernière visite \U0001F418"
+      title: Pendant votre absence…
     favourite:
       body: "%{name} a ajouté votre post à ses favoris :"
       subject: "%{name} a ajouté votre post à ses favoris"
+      title: Nouveau favori
     follow:
       body: "%{name} vous suit !"
       subject: "%{name} vous suit"
+      title: Nouvel·le abonné·e
     follow_request:
+      action: Gérer les demandes d'abonnement
       body: "%{name} a demandé à vous suivre"
       subject: 'Abonné⋅es en attente : %{name}'
+      title: Nouvelle demande d'abonnement
     mention:
+      action: Répondre
       body: "%{name} vous a mentionné⋅e dans :"
       subject: "%{name} vous a mentionné·e"
+      title: Nouvelle mention
     reblog:
       body: "%{name} a partagé votre statut :"
       subject: "%{name} a partagé votre statut"
+      title: Nouveau partage
   number:
     human:
       decimal_units:
@@ -510,7 +548,9 @@ fr:
           trillion: T
           unit: ''
   pagination:
+    newer: Plus récent
     next: Suivant
+    older: Plus ancien
     prev: Précédent
     truncate: "&hellip;"
   preferences:
@@ -533,7 +573,7 @@ fr:
     reblog:
       title: "%{name} a partagé votre statut"
   remote_follow:
-    acct: Entrez votre pseudo@instance depuis lequel vous voulez suivre ce⋅tte utilisateur⋅rice
+    acct: Entrez votre pseudo@instance depuis lequel vous voulez suivre cet·te utilisateur⋅ice
     missing_resource: L’URL de redirection n’a pas pu être trouvée
     proceed: Continuez pour suivre
     prompt: 'Vous allez suivre :'
@@ -545,12 +585,14 @@ fr:
       blackberry: Blackberry
       chrome: Chrome
       edge: Microsoft Edge
+      electron: Electron
       firefox: Firefox
       generic: Navigateur inconnu
       ie: Internet Explorer
       micro_messenger: MicroMessenger
       nokia: Nokia S40 Ovi Browser
       opera: Opera
+      otter: Autre
       phantom_js: PhantomJS
       qq: QQ Browser
       safari: Safari
@@ -610,7 +652,7 @@ fr:
       unlisted_long: Tout le monde peut voir vos statuts mais ils ne seront pas sur listés sur les fils publics
   stream_entries:
     click_to_show: Cliquer pour afficher
-    pinned: Statut épinglé
+    pinned: Pouet épinglé
     reblogged: partagé
     sensitive_content: Contenu sensible
   terms:
@@ -702,7 +744,31 @@ fr:
     recovery_instructions_html: Si vous perdez l’accès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour retrouver l’accès à votre compte. <strong>Conservez les codes de récupération en sécurité</strong>. Par exemple, en les imprimant et en les stockant avec vos autres documents importants.
     setup: Installer
     wrong_code: Les codes entrés sont incorrects ! L’heure du serveur et celle de votre appareil sont-elles correctes ?
+  user_mailer:
+    backup_ready:
+      explanation: Vous avez demandé une sauvegarde complète de votre compte Mastodon. Elle est maintenant prête à être téléchargée !
+      subject: Votre archive est prête à être téléchargée
+      title: Retrait de l'archive
+    welcome:
+      edit_profile_action: Configuration du profil
+      edit_profile_step: Vous pouvez personnaliser votre profil en téléchargeant un avatar, une image d'en-tête, en changeant votre pseudo et plus encore. Si vous souhaitez examiner les nouveaux abonnés avant qu'ils ne soient autorisés à vous suivre, vous pouvez verrouiller votre compte.
+      explanation: Voici quelques conseils pour vous aider à démarrer
+      final_action: Commencer à publier
+      final_step: 'Commencez à poster ! Même sans abonné·es, vos messages publics peuvent être vus par d''autres, par exemple sur la chronologie locale et dans les hashtags. Vous pouvez vous présenter sur le hashtag #introductions.'
+      full_handle: Votre pleine maîtrise
+      full_handle_hint: C'est ce que vous diriez à vos amis pour qu'ils puissent vous envoyer un message ou vous suivre à partir d'une autre instance.
+      review_preferences_action: Modifier les préférences
+      review_preferences_step: Assurez-vous de définir vos préférences, telles que les courriels que vous aimeriez recevoir ou le niveau de confidentialité auquel vous aimeriez que vos messages soient soumis par défaut. Si vous n'avez pas le mal des transports, vous pouvez choisir d'activer la lecture automatique GIF.
+      subject: Bienvenue sur Mastodon
+      tip_bridge_html: Si vous venez de Twitter, vous pouvez retrouver vos amis sur Mastodon en utilisant le <a href="%{bridge_url}">bridge app</a>. Cela ne fonctionne que s'ils ont aussi utilisé cette application !
+      tip_federated_timeline: La chronologie fédérée est une vue en direct du réseau Mastodon. Mais elle n'inclut que les personnes auxquelles vos voisin·es sont abonné·es, donc elle n'est pas complète.
+      tip_following: Vous suivez les administrateurs et administratrices de votre serveur par défaut. Pour trouver d'autres personnes intéressantes, consultez les chronologies locales et fédérées.
+      tip_local_timeline: La chronologie locale est une vue des personnes sur %{instance}. Ce sont vos voisines et voisins immédiats !
+      tip_mobile_webapp: Si votre navigateur mobile vous propose d'ajouter Mastodon à votre écran d'accueil, vous pouvez recevoir des notifications push. Il agit comme une application native de bien des façons !
+      tips: Astuces
+      title: Bienvenue à bord, %{name} !
   users:
     invalid_email: L’adresse courriel est invalide
     invalid_otp_token: Le code d’authentification à deux facteurs est invalide
+    seamless_external_login: Vous êtes connecté via un service externe, donc les paramètres concernant le mot de passe et le courriel ne sont pas disponibles.
     signed_in_as: 'Connecté·e en tant que :'
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index 100e2954c..30b68d7d6 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -23,7 +23,6 @@ gl:
       real_conversation_title: Construído para conversacións reais
       within_reach_body: Existen múltiples aplicativos para iOS, Android e outras plataformas grazas a un entorno API amigable para o desenvolvedor que lle permite estar ao tanto cos seus amigos en calquer lugar.
       within_reach_title: Sempre en contacto
-    find_another_instance: Atope outra instancia
     generic_description: "%{domain} é un servidor na rede"
     hosted_on: Mastodon aloxado en %{domain}
     learn_more: Coñeza máis
@@ -274,6 +273,9 @@ gl:
       contact_information:
         email: e-mail de traballo
         username: Nome de usuaria de contacto
+      hero:
+        desc_html: Mostrado na portada. Recoméndase 600x100px como mínimo. Si non se establece, mostrará a imaxe por omisión da instancia
+        title: Imáxe Heróe
       peers_api_enabled:
         desc_html: Nome de dominio que esta instancia atopou no fediverso
         title: Publicar lista de instancias descubertas
@@ -290,6 +292,9 @@ gl:
         open:
           desc_html: Permitir que calquera poida crear unha conta
           title: Abrir rexistro
+      show_known_fediverse_at_about_page:
+        desc_html: Si activado, mostraralle os toots de todo o fediverso coñecido nunha vista previa. Si non só mostrará os toots locais.
+        title: Mostrar vista previa do fediverso na liña temporal
       show_staff_badge:
         desc_html: Mostrar unha insignia de membresía nunha páxina de usuaria
         title: Mostrar insigna de membresía
@@ -338,9 +343,12 @@ gl:
       body: "%{reporter} informou sobre %{target}"
       subject: Novo informe sobre %{instance} (#%{id})
   application_mailer:
+    notification_preferences: Cambiar os axustes de correo-e
     salutation: "%{name},"
     settings: 'Mudar as preferencias de e-mail: %{link}'
     view: 'Vista:'
+    view_profile: Ver perfil
+    view_status: Ver estado
   applications:
     created: Creouse con éxito este aplicativo
     destroyed: Eliminouse con éxito o aplicativo
@@ -351,7 +359,8 @@ gl:
     your_token: O seu testemuño de acceso
   auth:
     agreement_html: Rexistrándose acorda seguir <a href="%{rules_path}">as normas da instancia</a> e <a href="%{terms_path}">os termos do servizo</a>.
-    change_password: Seguridade
+    change_password: Contrasinal
+    confirm_email: Confirmar correo-e
     delete_account: Eliminar conta
     delete_account_html: Se desexa eliminar a súa conta, pode <a href="%{path}">facelo aquí</a>. Pediráselle confirmación.
     didnt_get_confirmation: Non recibeu as instruccións de confirmación?
@@ -361,9 +370,16 @@ gl:
     logout: Desconectar
     migrate_account: Mover a unha conta diferente
     migrate_account_html: Si desexa redirixir esta conta hacia outra diferente, pode <a href="%{path}">configuralo aquí</a>.
+    or: ou
+    or_log_in_with: ou conectar con
+    providers:
+      cas: CAS
+      saml: SAML
     register: Rexistro
+    register_elsewhere: Rexístrese en outro servidor
     resend_confirmation: Voltar a enviar intruccións de confirmación
     reset_password: Restablecer contrasinal
+    security: Seguridade
     set_new_password: Establecer novo contrasinal
   authorize_follow:
     error: Desgraciadamente, algo fallou ao buscar a conta remota
@@ -410,6 +426,13 @@ gl:
       title: Esta páxina non é correcta
     noscript_html: Para utilizar a aplicación web de Mastodon debe habilitar JavaScript. De xeito alternativo, intente unha das <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">apps nativas</a> para Mastodon da súa plataforma.
   exports:
+    archive_takeout:
+      date: Data
+      download: Descargue o seu ficheiro
+      hint_html: Pode solicitar un ficheiro cos <strong>seus toots ficheiros de medios</strong>. Os datos estarán en formato ActivityPub e son compatibles con calquer software que o cumpla.
+      in_progress: Xerando o seu ficheiro...
+      request: Solicite o ficheiro
+      size: Tamaño
     blocks: A bloquear
     csv: CSV
     follows: A seguir
@@ -463,7 +486,7 @@ gl:
       expires_at: Caduca
       uses: Usos
     title: Convidar xente
-  landing_strip_html: "<strong>%{name}</strong> é unha usuaria en %{link_to_root_path}.  Pode seguilas ou interactuar con elas si ten unha conta en algún lugar do fediverso."
+  landing_strip_html: "<strong>%{name}</strong> é unha usuaria en %{link_to_root_path}.  Pode seguila ou interactuar con ela si ten unha conta en algún lugar do fediverso."
   landing_strip_signup_html: Si non, pode <a href="%{sign_up_path}">rexistrarse aquí</a>.
   lists:
     errors:
@@ -481,29 +504,38 @@ gl:
     title: Moderación
   notification_mailer:
     digest:
-      body: 'Aquí ten un breve sumario do que perdeu en %{instance} desde a última visita en %{since}:'
+      action: Ver todas as notificacións
+      body: Aquí ten un breve resumo das mensaxes publicadas desde a súa última visita en %{since}
       mention: "%{name} mencionouna en:"
       new_followers_summary:
-        one: Ten unha nova seguidora! Ben!
-        other: Obtivo %{count} novas seguidoras! Tremendo!
+        one: Ademáis, ten unha nova seguidora desde entón! Ben!
+        other: Ademáis, obtivo %{count} novas seguidoras desde entón! Tremendo!
       subject:
         one: "1 nova notificación desde a súa última visita \U0001F418"
         other: "%{count} novas notificacións desde a súa última visita \U0001F418"
+      title: Na súa ausencia…
     favourite:
       body: 'O seu estado foi marcado favorito por %{name}:'
       subject: "%{name} marcou favorito o seu estado"
+      title: Nova favorita
     follow:
       body: "%{name} agora está a seguila!"
       subject: "%{name} agora está a seguila"
+      title: Nova seguidora
     follow_request:
+      action: Xestionar peticións de seguimento
       body: "%{name} solicitou poder seguila"
       subject: 'Seguidora pendente: %{name}'
+      title: Nova petición de seguimento
     mention:
+      action: Respostar
       body: 'Foi mencionada por %{name} en:'
       subject: Vostede foi mencionada por %{name}
+      title: Nova mención
     reblog:
       body: 'O seu estado foi promocionado por %{name}:'
-      subject: "%{name} promocionou o seu estado"
+      subject: "%{name} promoveu o seu estado"
+      title: Nova promoción
   number:
     human:
       decimal_units:
@@ -516,7 +548,9 @@ gl:
           trillion: T
           unit: " "
   pagination:
+    newer: Máis novo
     next: Seguinte
+    older: Máis antigo
     prev: Previo
     truncate: "&hellip;"
   preferences:
@@ -537,7 +571,7 @@ gl:
       action_favourite: Favorito
       title: "%{name} mencionouna"
     reblog:
-      title: "%{name} promocionou un dos seus estados"
+      title: "%{name} promoveu un dos seus estados"
   remote_follow:
     acct: Introduza o seu nomedeusuaria@dominio desde onde quere facer seguimento
     missing_resource: Non se puido atopar o URL de redirecionamento requerido para a súa conta
@@ -551,12 +585,14 @@ gl:
       blackberry: Blackberry
       chrome: Chrome
       edge: Microsoft Edge
+      electron: Electron
       firefox: Firefox
       generic: Navegador descoñecido
       ie: Internet Explorer
       micro_messenger: MicroMessenger
       nokia: Navegador Nokia S40 Ovi
       opera: Opera
+      otter: Otter
       phantom_js: PhantomJS
       qq: Navegador QQ
       safari: Safari
@@ -710,7 +746,31 @@ gl:
     recovery_instructions_html: Si perdese o acceso ao seu teléfono, pode utilizar un dos códigos inferiores de recuperación para recuperar o acceso a súa conta. <strong>Garde os códigos en lugar seguro</strong>. Por exemplo, pode imprimilos e gardalos xunto con outros documentos importantes.
     setup: Configurar
     wrong_code: O código introducido non é válido! Son correctas as horas no dispositivo e o servidor?
+  user_mailer:
+    backup_ready:
+      explanation: Solicitou un respaldo completo da súa conta de Mastodon. Xa está listo para descargar!
+      subject: O seu ficheiro xa está listo para descargar
+      title: Leve o ficheiro
+    welcome:
+      edit_profile_action: Configurar perfil
+      edit_profile_step: Vostede pode personalizar o seu perfil subindo un avatar, cabeceira, cambiar o seu nome público e aínda máis. Si restrinxe a súa conta pode revisar a conta das personas que solicitan seguilas antes de permitirlles o acceso aos seus toots.
+      explanation: Aquí ten alunhas endereitas para ir aprendendo
+      final_action: Comece a publicar
+      final_step: 'Publique! Incluso sin seguidoras as súas mensaxes serán vistas por outras, por exemplo na liña temporal local e nas etiquetas. Podería presentarse no #fediverso utilizando a etiqueta #introductions.'
+      full_handle: O seu alcume completo
+      full_handle_hint: Esto é o que lle dirá aos seus amigos para que poidan seguila ou enviarlle mensaxes desde outra instancia.
+      review_preferences_action: Cambiar preferencias
+      review_preferences_step: Lembre establecer as preferencias, tales como qué correos-e lle querería recibir, ou o nivel de intimidade por omisión para as súas mensaxes. Se non lle molestan as imaxes con movemento, pode escoller que os GIF se reproduzan automáticamente.
+      subject: Benvida a Mastodon
+      tip_bridge_html: Si chega desde a Twitter, pode atopar aos seus amigos en Mastodon utilizando o <a href="%{bridge_url}">aplicativo ponte</a>. Só funciona si eles tamén utilizan o aplicativo ponte!
+      tip_federated_timeline: A liña temporal federada é unha visión ampla da rede Mastodon. Pero so inclúe xente a que segue xente que vostede segue, así que non é completa.
+      tip_following: Por omisión vostede segue ao Admin do seu servidor. Para atopar máis xente interesante, mire nas liñas temporais local e federada.
+      tip_local_timeline: A liña temporal local é unha ollada xeral sobre a xente en %{instance}. Son as súas veciñas máis próximas!
+      tip_mobile_webapp: Si o navegador móbil lle ofrece engadir Mastodon a pantalla de inicio, pode recibir notificacións push. En moitos aspectos comportarase como un aplicativo nativo!
+      tips: Consellos
+      title: Benvida, %{name}!
   users:
     invalid_email: O enderezo de correo non é válido
     invalid_otp_token: Código de doble-factor non válido
+    seamless_external_login: Está conectado a través de un servizo externo, polo que os axustes de contrasinal e correo-e non están dispoñibles.
     signed_in_as: 'Rexistrada como:'
diff --git a/config/locales/he.yml b/config/locales/he.yml
index 1f27dda7a..1a7c84d7c 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -23,7 +23,6 @@ he:
       real_conversation_title: בנוי לשיחות אמתיות
       within_reach_body: שלל אפליקציות עבור iOS, אנדרואיד ופלטפורמות אחרות שיאפשרו לך לשמור על קשר עם חברים בכל מקום, תודות למערכת מנשקי תוכנה ידידותיים למפתחים.
       within_reach_title: תמיד במרחק נגיעה
-    find_another_instance: לאיתור שרת אחר
     generic_description: "%{domain} הוא שרת אחד בתוך הרשת"
     hosted_on: מסטודון שיושב בכתובת %{domain}
     learn_more: מידע נוסף
@@ -233,7 +232,6 @@ he:
   applications:
     invalid_url: כתובת הקישורית אינה חוקית
   auth:
-    change_password: החלפת סיסמא
     didnt_get_confirmation: לא התקבלו הוראות אימות?
     forgot_password: הנשתכחה סיסמתך?
     login: כניסה
@@ -241,6 +239,7 @@ he:
     register: הרשמה
     resend_confirmation: שלח הוראות אימות בשנית
     reset_password: איפוס סיסמא
+    security: החלפת סיסמא
     set_new_password: שינוי סיסמא
   authorize_follow:
     error: למרבה הצער, היתה שגיאה בחיפוש החשבון המרוחק
diff --git a/config/locales/hr.yml b/config/locales/hr.yml
index a3c9aa436..2d2eddc08 100644
--- a/config/locales/hr.yml
+++ b/config/locales/hr.yml
@@ -30,7 +30,6 @@ hr:
   applications:
     invalid_url: Uneseni link nije valjan
   auth:
-    change_password: Vjerodajnica
     didnt_get_confirmation: Niste primili instrukcije za potvrđivanje?
     forgot_password: Zaboravljena lozinka?
     login: Prijavi se
@@ -38,6 +37,7 @@ hr:
     register: Registriraj se
     resend_confirmation: Ponovo pošalji instrukcije za potvrđivanje
     reset_password: Resetiraj lozinku
+    security: Vjerodajnica
     set_new_password: Postavi novu lozinku
   authorize_follow:
     error: Nažalost, došlo je do greške looking up the remote račun
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index 586503a35..6be82c1de 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -1,48 +1,520 @@
 ---
 hu:
   about:
+    about_hashtag_html: Ezek a <strong>#%{hashtag}</strong> címkével ellátott publikus tülkök. Reagálhatsz rájuk, ha már van felhasználói fiókod valahol a föderációban.
     about_mastodon_html: Mastodon egy <em>szabad, nyílt forráskódú</em> szociális hálózati kiszolgálo. Egy <em>központosítatlan</em> alternatíva a kereskedelmi platformokra, elkerüli a kommunikációd monopolizációját veszélyét. Bárki futtathatja a Mastodon-t és részt vehet a <em>szociális hálózatban</em>.
+    about_this: Rólunk
+    closed_registrations: A regisztráció jelenleg nem engedélyezett ezen az instancián. De ne csüggedj! Létrehozhatsz fiókot egy másik instancián és azon keresztül is hozzáférsz a teljes föderációhoz.
+    contact: Kapcsolat
+    contact_missing: Nincs megadva
+    contact_unavailable: N/A
+    description_headline: Mi az a %{domain}?
+    domain_count_after: további instanciával
+    domain_count_before: Kapcsolatban
+    extended_description_html: |
+      <h3>Ez itt a szabályzat helye</h3>
+      <p>Még nem állítottál be bővebb leírást.</p>
+    features:
+      humane_approach_body: Más alkalmazások hibáiból tanulva a Mastodon etikus alapokon nyugvó döntésekkel küzd a közösségi média ártalmai ellen.
+      humane_approach_title: Emberséges attitűd
+      not_a_product_body: A Mastodon nem a profitszerzésre épül, nem is privát játszótér. Nincsenek reklámok, nincs adatbányászat és központosított döntéshozatal sincsen.
+      not_a_product_title: Ember vagy, nem pedig árucikk
+      real_conversation_body: Az 500 karakteres limit, az érzékeny tartalomként jelölés és más kifinomult eszközök segítségével tényleg egyedi módon fejezheted ki önmagad.
+      real_conversation_title: Valódi beszélgetésekre tervezve
+      within_reach_body: A fejlesztőbarát API-nak köszönhetően számos iOS, Android és egyéb platformra írt alkalmazás teszi lehetővé, hogy bármikor, bárhonnan részt vehess a társalgásban.
+      within_reach_title: Mindig elérhetőnek lenni
+    generic_description: "%{domain} csak egy a számtalan szerver közül a föderációban"
+    hosted_on: "%{domain} Mastodon instancia"
+    learn_more: Tudj meg többet
+    other_instances: Instanciák listája
     source_code: Forráskód
+    status_count_after: tülköt küldött
+    status_count_before: eddig
+    user_count_after: felhasználónk
+    user_count_before: Összesen
+    what_is_mastodon: Mi a Mastodon?
   accounts:
     follow: Követés
     followers: Követők
     following: Követed őket
+    media: Média
+    moved_html: "%{name} ide költözött: %{new_profile_link}"
     nothing_here: Nincs itt semmi!
     people_followed_by: "%{name} követett személyei"
     people_who_follow: "%{name} követői"
-    posts: Bejegyzések
+    posts: Tülkök
+    posts_with_replies: Tülkök és válaszok
+    remote_follow: Követés más hálózaton
+    reserved_username: Ez egy már lefoglalt felhasználónév
+    roles:
+      admin: Adminisztrátor
+      moderator: Moderátor
     unfollow: Követés abbahagyása
+  admin:
+    account_moderation_notes:
+      account: Moderátor
+      create: Új bejegyzés
+      created_at: Dátum
+      created_msg: Moderációs bejegyzés létrehozva!
+      delete: Törlés
+      destroyed_msg: Moderációs bejegyzés törölve!
+    accounts:
+      are_you_sure: Biztos vagy benne?
+      by_domain: Domain
+      confirm: Megerősítés
+      confirmed: Megerősítve
+      demote: Lefokozás
+      disable: Kikapcsolás
+      disable_two_factor_authentication: Kétlépcsős azonosítás kikapcsolása
+      disabled: Kikapcsolva
+      display_name: Megjelenített név
+      domain: Domain
+      edit: Szerkesztés
+      email: E-mail
+      enable: Engedélyezés
+      enabled: Engedélyezve
+      feed_url: Hírcsatorna URL
+      followers: Követők
+      followers_url: Követők URL
+      follows: Követettek
+      inbox_url: Beérkezett üzenetek URL
+      ip: IP
+      location:
+        all: Összes
+        local: Helyi
+        remote: Távoli
+        title: Hely
+      login_status: Bejelentkezve
+      media_attachments: Média-csatolmányok
+      memorialize: Emlékállítás
+      moderation:
+        all: Összes
+        silenced: Némítva
+        suspended: Felfüggesztve
+        title: Moderáció
+      moderation_notes: Moderációs bejegyzés
+      most_recent_activity: Legutóbbi tevékenységek
+      most_recent_ip: Legutóbbi IP-cím
+      not_subscribed: Nincs feliratkozás
+      order:
+        alphabetic: Alfabetikus
+        most_recent: Legutóbbi
+        title: Rendezés
+      outbox_url: Kimenő üzenetek URL
+      perform_full_suspension: Teljes felfüggesztés
+      profile_url: Profil URL
+      promote: Előléptetés
+      protocol: Protokoll
+      public: Nyilvános
+      push_subscription_expires: A PuSH feliratkozás elévül
+      redownload: Profilkép frissítése
+      reset: Visszaállítás
+      reset_password: Jelszó visszaállítása
+      resubscribe: Feliratkozás ismét
+      role: Engedélyek
+      roles:
+        admin: Adminisztrátor
+        moderator: Moderátor
+        staff: Stáb
+        user: Felhasználó
+      salmon_url: Salmon URL
+      search: Keresés
+      shared_inbox_url: Bejövő üzenetek URL keresése
+      show:
+        created_reports: Ezen fiók által létrehozott jelentések
+        report: jelentés
+        targeted_reports: Jelentések ezzel a fiókkal kapcsolatban
+      silence: Némítás
+      statuses: Tülkök
+      subscribe: Feliratkozás
+      title: Fiókok
+      undo_silenced: Némítás visszavonása
+      undo_suspension: Felfüggesztés visszavonása
+      unsubscribe: Leiratkozás
+      username: Felhasználónév
+      web: Weboldal
+    action_logs:
+      actions:
+        confirm_user: "%{name} megerősítette e-mail címét: %{target}"
+        create_custom_emoji: "%{name} új hangulatjelet töltött fel: %{target}"
+        create_domain_block: "%{name} letiltotta az alábbi domaint: %{target}"
+        create_email_domain_block: "%{name} feketelistára tette az alábbi e-mail domaint: %{target}"
+        demote_user: "%{name} lefokozta az alábbi felhasználót: %{target}"
+        destroy_domain_block: "%{name} engedélyezte az alábbi domaint: %{target}"
+        destroy_email_domain_block: "%{name} fehérlistára tette az alábbi e-mail domaint: %{target}"
+        destroy_status: "%{name} eltávolította az alábbi felhasználó tülkjét: %{target}"
+        disable_2fa_user: "%{name} kikapcsolta a kétlépcsős azonosítást %{target} felhasználó fiókján"
+        disable_custom_emoji: "%{name} letiltotta az alábbi hangulatjelet: %{target}"
+        disable_user: "%{name} letiltotta az alábbi felhasználó bejelentkezését: %{target}"
+        enable_custom_emoji: "%{name} engedélyezte az alábbi hangulatjelet: %{target}"
+        enable_user: "%{name} engedélyezte az alábbi felhasználó bejelentkezését: %{target}"
+        memorialize_account: "%{name} emléket állított az alábbi felhasználónak: %{target}"
+        promote_user: "%{name} előléptette az alábbi felhasználót: %{target}"
+        reset_password_user: "%{name} visszaállította az alábbi felhasználó jelszavát: %{target}"
+        resolve_report: "%{name} mellőzte az alábbi jelentést: %{target}"
+        silence_account: "%{name} lenémította %{target} felhasználói fiókját"
+        suspend_account: "%{name} felfüggesztette %{target} felhasználói fiókját"
+        unsilence_account: "%{name} feloldotta a némítást %{target} felhasználói fiókján"
+        unsuspend_account: "%{name} feloldotta %{target} felhasználói fiókjának felfüggesztését"
+        update_custom_emoji: "%{name} frissítette az alábbi hangulatjelet: %{target}"
+        update_status: "%{name} frissítette %{target} felhasználó tülkjét"
+      title: Audit napló
+    custom_emojis:
+      by_domain: Domain
+      copied_msg: Sikeresen létrehoztuk a hangulatjel helyi másolatát
+      copy: Másolás
+      copy_failed_msg: Hangulatjel helyi másolatának létrehozása sikertelen
+      created_msg: Hangulatjel létrehozva!
+      delete: Törlés
+      destroyed_msg: A hangulatjel törlése sikeres!
+      disable: Letiltás
+      disabled_msg: Hangulatjel letiltva
+      emoji: Hangulatjel
+      enable: Engedélyezés
+      enabled_msg: Hangulatjel engedélyezve
+      image_hint: PNG (maximális méret 50KB)
+      listed: Listázva
+      new:
+        title: Új egyedi hangulatjel hozzáadása
+      overwrite: Felülírás
+      shortcode: Shortcode
+      shortcode_hint: Legalább két karakter, csak betűk, számok és alsóvonás
+      title: Egyedi hangulatjelek
+      unlisted: Nincs listázva
+      update_failed_msg: Nem sikerült frissíteni a hangulatjelet
+      updated_msg: Hangulatjel sikeresen frissítve!
+      upload: Feltöltés
+    domain_blocks:
+      add_new: Új hozzáadása
+      created_msg: A domain-tiltás feldolgozása folyamatban
+      destroyed_msg: A domain tiltása feloldva
+      domain: Domain
+      new:
+        create: Tiltás létrehozása
+        hint: A domain-tiltás nem gátolja meg az új fiókok hozzáadását az abatbázishoz, de visszamenőlegesen és automatikusan aktivál bizonyos moderációs szabályokat ezen fiókok esetében.
+        severity:
+          desc_html: A <strong>Némítás</strong> elrejti az adott felhasználó tülkjeit mindenki elől, aki nem követi az adott felhasználót. A <strong>Felfüggesztés</strong> eltávolítja az adott felhasználó által létrehozott minden tartalmat, ide értve a médiafájlokat és a fiókadatokat is. Válaszd az <strong>Egyik sem</strong> opciót, ha csupán a médiafájlokat szeretnéd elutasítani.
+          noop: Egyik sem
+          silence: Némítás
+          suspend: Felfüggesztés
+        title: Új domain-tiltás
+      reject_media: Médiafájlok elutasítása
+      reject_media_hint: Eltávolítja a helyben tárolt médiafájlokat és a továbbiakban letiltja az új médiafájlok letöltését. Felfüggesztett fiókok esetében irreleváns opció
+      severities:
+        noop: Egyik sem
+        silence: Némítás
+        suspend: Felfüggesztés
+      severity: Súlyosság
+      show:
+        affected_accounts:
+          one: Összesen egy fiók érintett az adatbázisban
+          other: Összesen %{count} fiók érintett az adatbázisban
+        retroactive:
+          silence: Minden felhasználó némításának feloldása ezen a domainen
+          suspend: Minden felhasználó felfüggesztésének feloldása ezen a domainen
+        title: "%{domain} domain tiltásának feloldása"
+        undo: Visszavonás
+      title: Tiltott domainek
+      undo: Visszavonás
+    email_domain_blocks:
+      add_new: Új hozzáadása
+      created_msg: E-mail domain sikeresen hozzáadva a feketelistához
+      delete: Törlés
+      destroyed_msg: E-mail domain sikeresen eltávolítva a feketelistáról
+      domain: Domain
+      new:
+        create: Domain hozzáadása
+        title: Új e-mail feketelista bejegyzés
+      title: E-mail feketelista
+    instances:
+      account_count: Nyilvántartott fiókok
+      domain_name: Domain
+      reset: Visszaállítás
+      search: Keresés
+      title: Nyilvántartott instanciák
+    invites:
+      filter:
+        all: Összes
+        available: Elérhető
+        expired: Elévült
+        title: Szűrő
+      title: Meghívások
+    reports:
+      action_taken_by: 'Kezelte:'
+      are_you_sure: Biztos vagy benne?
+      comment:
+        label: Hozzászólás
+        none: Egyik sem
+      delete: Törlés
+      id: ID
+      mark_as_resolved: Megjelölés megoldottként
+      nsfw:
+        'false': Média-csatolmányok rejtésének feloldása
+        'true': Média-csatolmányok elrejtése
+      report: "#%{id} számú jelentés"
+      report_contents: Tartalom
+      reported_account: Bejelentett fiók
+      reported_by: 'Jelentette:'
+      resolved: Megoldott
+      silence_account: Felhasználó némítása
+      status: Állapot
+      suspend_account: Felhasználó felfüggesztése
+      target: Cél
+      title: Jelentések
+      unresolved: Megoldatlan
+      view: Megtekintés
+    settings:
+      activity_api_enabled:
+        desc_html: Helyi tülkök, aktív felhasználók és új regisztrációk száma heti bontásban
+        title: Felhasználói aktivitás összesített statisztikájának publikussá tétele
+      bootstrap_timeline_accounts:
+        desc_html: Az egyes felhasználónevek vesszővel elválasztva. Csak helyi és aktivált fiókok esetében működik. Üresen (alapértelmezettként) minden helyi adminisztrátorra érvényes.
+        title: Alapértelmezett követések új felhasználók esetében
+      contact_information:
+        email: Kapcsolattartói e-mail cím
+        username: Kapcsolattartó felhasználóneve
+      peers_api_enabled:
+        desc_html: Domainek, amelyekkel ez az instancia kapcsolatban áll
+        title: Instanciák listájának közzététele, melyekkel ez a szerver kapcsolatban áll
+      registrations:
+        closed_message:
+          desc_html: Ez az üzenet jelenik meg a főoldalon, ha a regisztráció nem engedélyezett. HTML-tageket is használhatsz
+          title: Üzenet, ha a regisztráció nem engedélyezett
+        deletion:
+          desc_html: Engedélyezed a felhasználóknak, hogy töröljék fiókjukat
+          title: Fiók törlésének engedélyezése
+        min_invite_role:
+          disabled: Senkinek
+          title: Meghívások engedélyezése
+        open:
+          desc_html: Bárki létrehozhat felhasználói fiókot
+          title: Nyitott regisztráció
+      show_staff_badge:
+        desc_html: Stáb-jelvény megjelenítése a felhasználó oldalán
+        title: Stáb-jelvény megjelenítése
+      site_description:
+        desc_html: 'Rövid bemutatkozás a főoldalon és a meta fejlécekben. Az alábbi HTML-tageket használhatod: <code>&lt;a&gt;</code> és <code>&lt;em&gt;</code>.'
+        title: Az instancia bemutatása
+      site_description_extended:
+        desc_html: Ide teheted például a közösségi és egyéb szabályzatot, útmutatókat és mindent, ami egyedivé teszi instanciádat. HTML-tageket is használhatsz
+        title: További egyedi információ
+      site_terms:
+        desc_html: Megírhatod saját adatkezelési szabályzatodat, felhasználási feltételeidet vagy más hasonló jellegű dokumentumodat. HTML-tageket is használhatsz
+        title: Egyedi felhasználási feltételek
+      site_title: Az instancia neve
+      thumbnail:
+        desc_html: Az OpenGraph és API előnézetekhez használjuk. Ajánlott mérete 1200x560 pixel
+        title: Az instancia bélyegképe
+      timeline_preview:
+        desc_html: Publikus időfolyam megjelenítése a főoldalon
+        title: Időfolyam előnézete
+      title: Oldal beállításai
+    statuses:
+      back_to_account: Vissza a fiók oldalára
+      batch:
+        delete: Törlés
+        nsfw_off: Szenzitív tartalom kikapcsolva
+        nsfw_on: Szenzitív tartalom bekapcsolva
+      execute: Végrehajt
+      failed_to_execute: Végrehajtás sikertelen
+      media:
+        hide: Média elrejtése
+        show: Média megjelenítése
+        title: Média
+      no_media: Nem található médiafájl
+      title: Felhasználó tülkjei
+      with_media: Médiafájlokkal
+    subscriptions:
+      callback_url: Callback URL
+      confirmed: Megerősítve
+      expires_in: Elévül
+      last_delivery: Utolsó kézbesítés
+      title: WebSub
+      topic: Téma
+    title: Karbantartás
+  admin_mailer:
+    new_report:
+      body: "%{reporter} jelentette: %{target}"
+      subject: 'Új jelentés az alábbi instancián: %{instance} (#%{id})'
+  application_mailer:
+    notification_preferences: E-mail beállítások módosítása
+    salutation: "%{name},"
+    settings: 'E-mail beállítások módosítása: %{link}'
+    view: 'Megtekintés:'
+    view_profile: Profil megtekintése
+    view_status: Tülk megtekintése
+  applications:
+    created: Alkalmazás sikeresen létrehozva
+    destroyed: Alkalmazás sikeresen eltávolítva
+    invalid_url: A megadott URL nem megfelelő
+    regenerate_token: Hozzáférési kulcs újragenerálása
+    token_regenerated: Hozzáférési kulcs sikeresen újragenerálva
+    warning: Ez érzékeny adat. Soha ne oszd meg másokkal!
+    your_token: Hozzáférési kulcsod
   auth:
-    change_password: Jelszó változtatása
+    agreement_html: A feliratkozással elfogatod az <a href="%{rules_path}">instancia szabályzatát</a> és a <a href="%{terms_path}">felhasználási feltételeket</a>.
+    delete_account: Felhasználói fiók törlése
+    delete_account_html: Felhasználói fiókod törléséhez <a href="%{path}">kattints ide</a>. A rendszer újbóli megerősítést fog kérni.
     didnt_get_confirmation: Nem kaptad meg a megerősítési lépéseket?
     forgot_password: Elfelejtetted a jelszavad?
-    login: Belépés
+    invalid_reset_password_token: A jelszó-visszaállítási kulcs nem megfelelő vagy lejárt. Kérlek generálj egy újat.
+    login: Bejelentkezés
+    logout: Kijelentkezés
+    migrate_account: Felhasználói fiók költöztetése
+    migrate_account_html: Ha szeretnéd átirányítani ezt a fiókodat egy másikra, a beállításokat <a href="%{path}">itt találod meg</a>.
     register: Regisztráció
     resend_confirmation: Megerősítési lépések újraküldése
     reset_password: Jelszó visszaállítása
+    security: Biztonság
     set_new_password: Új jelszó beállítása
+  authorize_follow:
+    error: Hiba történt a távoli felhasználó keresésekor
+    follow: Követés
+    follow_request: 'Engedélyt kértél az alábbi felhasználó követésére:'
+    following: 'Siker! Mostantól követed az alábbi felhasználót:'
+    post_follow:
+      close: Akár be is zárhatod ezt az ablakot.
+      return: Visszatérés a felhasználó profiloldalára
+      web: Megtekintés a weben
+    title: "%{acct} követése"
+  datetime:
+    distance_in_words:
+      about_x_hours: "%{count}ó"
+      about_x_months: "%{count}h"
+      about_x_years: "%{count}é"
+      almost_x_years: "%{count}é"
+      half_a_minute: Épp most
+      less_than_x_minutes: "%{count}p"
+      less_than_x_seconds: Épp most
+      over_x_years: "%{count}é"
+      x_days: "%{count}n"
+      x_minutes: "%{count}p"
+      x_months: "%{count}h"
+      x_seconds: "%{count}mp"
+  deletes:
+    bad_password_msg: Haha, hekker! Helytelen jelszó
+    confirm_password: Személyazonosságod megerősítéséhez írd be a jelenlegi jelszavad
+    description_html: Ezzel <strong>véglegesen és visszafordíthatatlanul</strong> törlöd minden tartalmadat és deaktiválod a fiókodat. A felhasználónevedet megtartjuk, hogy megakadályozzuk a neveddel történő jövőbeni visszaélések lehetőségét.
+    proceed: Felhasználói fiók törlése
+    success_msg: Felhasználói fiókod sikeresen törölve lett
+    warning_html: Csak azt tudjuk garantálni, hogy az általad létrehozott tartalmat erről az instanciáról töröljük. Ha egyes tartalmaidat sokan megosztották, valószínűleg marad nyomuk a megosztások miatt. Nam fogjuk tudni frissíteni azon instanciák adatbázisát, amelyek nem kapcsolódnak a föderációhoz vagy amelyek leiratkoztak a tülkjeidről.
+    warning_title: Szórt tartalmak elérése
+  errors:
+    '403': Nincs jogosultságod az oldal megtekintéséhez.
+    '404': Az általad keresett oldal nem található.
+    '410': Az általad keresett oldal már nem létezik.
+    '422':
+      content: Megerősítés sikertelen. Nem tiltottad le esetleg a sütiket?
+      title: Megerősítés sikertelen
+    '429': Kampec
+    '500':
+      content: Sajnáljuk, valami hiba történt a mi oldalunkon.
+      title: Az oldal nem megfelelő
+    noscript_html: A Mastodon webalkalmazás használatához engedélyezned kell a JavaScriptet. A másik megoldás, hogy kipróbálod az egyik, a platformodnak megfelelő <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">alkalmazást</a>.
+  exports:
+    blocks: Tiltólistádon
+    csv: CSV
+    follows: Követettjeid
+    mutes: Némításaid
+    storage: Médiatároló
+  followers:
+    domain: Domain
+    explanation_html: Ahhoz, hogy biztosítsd a tülkjeid adatvédelmét, tudnod kell, kik követnek téged. <strong>Még privátnak jelölt tülkjeid is továbbítódnak minden instanciára, ahol követőid vannak</strong>. Az alábbi listában láthatod, melyek ezek az instanciák; eltávolíthatod őket, ha nem vagy biztos benne, hogy az adott instancia üzemeltetői tiszteletben tartják az adatvédelmi beállításaidat.
+    followers_count: Követők száma
+    lock_link: Fiókod priváttá tétele
+    purge: Eltávolítás a követőid közül
+    success:
+      one: Egy domainen található követőid tiltása folyamatban...
+      other: "%{count} domainen található követőid tiltása folyamatban..."
+    true_privacy_html: Tartsd észben, hogy <strong>valódi biztonság csak végponttól-végpontig titkosítással érhető el</strong>.
+    unlocked_warning_html: Bárki követhet és így azonnal láthatja a privát tülkjeid. A %{lock_link} funkció bekapcsolásával lehetőséged van egyenként felülvizsgálni a követési kérelmeket.
+    unlocked_warning_title: A fiókod jelenleg nem privát
   generic:
     changes_saved_msg: Változások sikeresen elmentve!
-    powered_by: powered by %{link}
+    powered_by: működteti a %{link}
     save_changes: Változások mentése
     validation_errors:
       one: Valami nincs rendjén! Kérlek tekintsd meg a hibát alant
       other: Valami nincs rendjén! Kérlek tekintsd meg a %{count} darab hibát alant.
-  landing_strip_html: "<strong>%{name}</strong> is a user on %{link_to_root_path}. You can follow them or interact with them if you have an account anywhere in the fediverse."
-  landing_strip_signup_html: If you don't, you can <a href="%{sign_up_path}">sign up here</a>.
+  imports:
+    preface: Itt importálhatod egy másik instanciáról lementett adataidat, például követettjeid és letiltott felhasználóid listáját.
+    success: Adataidat sikeresen feltöltöttük és feldolgozásukat megkezdtük
+    types:
+      blocking: Letiltottak listája
+      following: Követettjeid listája
+      muting: Némított felhasználók listája
+    upload: Feltöltés
+  in_memoriam_html: In Memoriam.
+  invites:
+    delete: Visszavonás
+    expired: Lejárt
+    expires_in:
+      '1800': 30 perc
+      '21600': 6 óra
+      '3600': 1 óra
+      '43200': 12 óra
+      '86400': 1 nap
+    expires_in_prompt: Soha
+    generate: Generálás
+    max_uses:
+      one: 1 felhasználás
+      other: "%{count} felhasználás"
+    max_uses_prompt: Nincs korlát
+    prompt: Az itt generált linkek megosztásával hívhatod meg ismerőseidet az instanciára
+    table:
+      expires_at: Lejárat
+      uses: Használat
+    title: Meghívások
+  landing_strip_html: "<strong>%{name}</strong> az alábbi instancia használója: %{link_to_root_path}. Követheted vagy kapcsolatba léphetsz vele, ha már van felhasználói fiókod a föderációban."
+  landing_strip_signup_html: Ha még nincs fiókod, <a href="%{sign_up_path}">itt regisztrálhatsz</a>.
+  lists:
+    errors:
+      limit: Elérted a hozzáadható listák maximális számát
+  media_attachments:
+    validations:
+      images_and_video: Nem csatolhatsz videót olyan tülkhöz, amelyhez már csatoltál képet
+      too_many: Maximum négy fájlt csatolhatsz a tülkhöz
+  migrations:
+    acct: Az új fiók felhasznalonev@domain formátumban
+    currently_redirecting: 'A profilod az alábbi fiókra van átirányítva:'
+    proceed: Mentés
+    updated_msg: Fiókod átirányítási beállítasait sikeresen mentettük!
+  moderation:
+    title: Moderáció
   notification_mailer:
+    digest:
+      action: Összes értesítés megtekintése
+      body: Itt a legutóbbi látogatásod (%{since}) óta írott üzenetek rövid összefoglalása
+      mention: "%{name} megemlített itt:"
+      new_followers_summary:
+        one: Sőt, egy új követőd is lett, amióta nem jártál itt. Hurrá!
+        other: Sőt, %{count} új követőd is lett, amióta nem jártál itt. Hihetetlen!
+      subject:
+        one: "Egy új értesítésed érkezett legutóbbi látogatásod óta \U0001F418"
+        other: "%{count} új értesítésed érkezett legutóbbi látogatásod óta \U0001F418"
+      title: Amíg távol voltál…
     favourite:
       body: 'Az állapotodat kedvencnek jelölte %{name}:'
       subject: "%{name} kedvencnek jelölte az állapotod"
+      title: Új kedvencnek jelölés
     follow:
       body: "%{name} mostantól követ téged!"
       subject: "%{name} mostantól követ téged"
+      title: Új követő
+    follow_request:
+      action: Követési kérések kezelése
+      body: "%{name} követni szeretne téged"
+      subject: 'Jóváhagyásra vár: %{name}'
+      title: Új követési kérés
     mention:
+      action: Válasz
       body: "%{name} megemlített téged:"
       subject: "%{name} megemlített téged"
+      title: Új említés
     reblog:
       body: 'Az állapotod reblogolta %{name}:'
       subject: "%{name} reblogolta az állapotod"
+      title: Új reblog
   number:
     human:
       decimal_units:
@@ -53,10 +525,226 @@ hu:
           quadrillion: Q
           thousand: K
           trillion: T
-          unit: ''
+          unit: " "
   pagination:
     next: Következő
     prev: Előző
+    truncate: "&hellip;"
+  preferences:
+    languages: Nyelvek
+    other: Egyéb
+    publishing: Közzététel
+    web: Web
+  push_notifications:
+    favourite:
+      title: "%{name} a kedvenceihez adta a tülköd"
+    follow:
+      title: "%{name} mostantól követ téged"
+    group:
+      title: "%{count} értesítés"
+    mention:
+      action_boost: Reblog
+      action_expand: Mutass többet
+      action_favourite: Kedvencekhez adás
+      title: "%{name} megemlített téged"
+    reblog:
+      title: "%{name} reblogolta a tülköd"
+  remote_follow:
+    acct: Írd be a felhasználódat, amelyről követni szeretnéd felhasznalonev@domain formátumban
+    missing_resource: A fiókodnál nem található a szükséges átirányítási URL
+    proceed: Tovább a követéshez
+    prompt: 'Őt tervezed követni:'
+  sessions:
+    activity: Legutóbbi tevékenység
+    browser: Böngésző
+    browsers:
+      alipay: Alipay
+      blackberry: Blackberry
+      chrome: Chrome
+      edge: Microsoft Edge
+      electron: Electron
+      firefox: Firefox
+      generic: Ismeretlen böngésző
+      ie: Internet Explorer
+      micro_messenger: MicroMessenger
+      nokia: Nokia S40 Ovi Böngésző
+      opera: Opera
+      otter: Otter
+      phantom_js: PhantomJS
+      qq: QQ Browser
+      safari: Safari
+      uc_browser: UCBrowser
+      weibo: Weibo
+    current_session: Jelenlegi munkamenet
+    description: "%{browser} az alábbi platformon: %{platform}"
+    explanation: Jelenleg az alábbi böngészőkkel vagy bejelentkezve a fiókodba.
+    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: ismeretlen platform
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
+    revoke: Visszavonás
+    revoke_success: Munkamenet sikeresen visszavonva
+    title: Munkamenetek
   settings:
+    authorized_apps: Jóváhagyott alkalmazások
+    back: Vissza a Mastodonhoz
+    delete: Fiók törlése
+    development: Fejlesztőknek
     edit_profile: Profil szerkesztése
-    preferences: Beállítások
+    export: Adatok exportálása
+    followers: Jóváhagyott követők
+    import: Importálás
+    migrate: Fiók átirányítása
+    notifications: Értesítések
+    preferences: Általános beállítások
+    settings: Beállítások
+    two_factor_authentication: Kétlépcsős azonosítás
+    your_apps: Alkalmazásaid
+  statuses:
+    open_in_web: Megnyitás a weben
+    over_character_limit: Túllépted a maximális %{max} karakteres keretet
+    pin_errors:
+      limit: Elérted a kitűzhető tülkök maximális számát
+      ownership: Nem tűzheted ki valaki más tülkjét
+      private: Csak publikus tülköt tűzhetsz ki
+      reblog: Reblogolt tülköt nem tudsz kitűzni
+    show_more: Mutass többet
+    title: '%{name}: "%{quote}"'
+    visibilities:
+      private: Csak követőknek
+      private_long: A tülk csak követőidnek jelenik meg
+      public: Nyilvános
+      public_long: Bárki láthatja a tülköt
+      unlisted: Listázatlan
+      unlisted_long: Mindenki látja, de a nyilvános időfolyamokban nem jelenik meg
+  stream_entries:
+    click_to_show: Megtekintéshez kattints
+    pinned: Kitűzött tülk
+    reblogged: reblogolt
+    sensitive_content: Szenzitív tartalom
+  terms:
+    body_html: |
+      <h2>Adatvédelmi és adatkezelési nyilatkozat</h2>
+
+      <h3 id="collect">Milyen információt gyűjtünk?</h3>
+
+      <p>Az oldalra történő regisztráció és a szolgáltatás használata - olvasás, tartalom létrehozása, tartalommegosztás - során információt gyűjtünk veled kapcsolatban.</p>
+
+      <p>A regisztráció során kérhetjük nevedet és e-mail címedet. Az oldalt természetesen regisztráció nélkül is felkeresheted. Az e-mail címed megerősítése egy egyedi információt tartalmazó link segítségével történik. Mondott linkre kattintva ellenőrizzük, hogy valóban te vagy a cím kezelője.</p>
+
+      <p>Regisztrált felhasználók esetében tülkök írásakor rögzítjük a felhasználó IP-címét. A szerver napjófájlja szintén tárolhatja ezt az IP-címet, valamint a szerverre érkező minden kérés küldő-oldali IP-címét.</p>
+
+      <h3 id="use">Mire használjuk a begyűjtött információt?</h3>
+
+      <p>Minden begyűjtött információt az alábbi okokból használhatunk fel:</p>
+
+      <ul>
+        <li>A felhasználói élmény személyre szabásához &mdash; a tőled gyűjtött információ segítségével biztosíthatjuk számodra az egyedi igényeknek történő megfelelést.</li>
+        <li>Szolgáltatásunk fejlesztéséhez &mdash; folyamatosan igyekszünk fejlődni és jobbá válni, és ezt a tőled kapott adatok és visszajelzések is nagyban segítik.</li>
+        <li>Az ügyféltámogatás fejlesztéséhez &mdash; a gyűjtött adatok segítségével hatékonyabban támogathatjuk felhasználóinkat, ha azok segítségre szorulnak.</li>
+        <li>E-mail értesítések küldéséhez &mdash; a megadott e-mail címedre küldjük ki az általad igényelt értesítéseket, a szolgáltatásra vonatkozó információkat és a válaszokat a tőled beérkező megkeresésekre.</li>
+      </ul>
+
+      <h3 id="protect">Hogyan védjük a tőled gyűjtött információt?</h3>
+
+      <p>Bitonsági mechanizmusok egész sorát vetjük be annak érdekében, hogy biztosítsuk a tőled származó személyes és használatai adatok és információk biztonságát.</p>
+
+      <h3 id="data-retention">Meddig tároljuk a tőled származó adatokat?</h3>
+
+      <p>Minden tőlünk telhetőt megteszünk annak érdekében, hogy</p>
+
+      <ul>
+        <li>a szerver naplófájljaiban tárolt, a szerverre érkező kérések küldő-oldali IP-címét maximum 90 napig,</li>
+        <li>a regisztrált felhasználók tülkjeinek eredeti IP-címét pedig maximum 5 évig</li>
+      </ul>
+
+      <p>tároljuk.</p>
+
+      <h3 id="cookies">Használunk-e sütiket?</h3>
+
+      <p>Igen. A sütik olyan kisméretű fájlok, amelyeket a szolgáltatások vagy internet-szolgáltatók küldenek a felhasználó számítógépére a böngészőn keresztül (természetesen csak abban az esetben, ha a felhasználó ezt engedélyezi). Oldalunk ezen sütik segítségével ismerik fel a böngésződet és - amennyiben rendelkezel nálunk fiókkal - kötik össze azt a felhasználói fiókoddal.</p>
+
+      <p>A sütik segítségével jobban megérthetjük használati szokásaidat, eltárolhatjuk beállításaidat következő látogatásodig, valamint így mérhetjük az oldal látogatottságát és használatát, mely adatok segítenek abban, hogy jobbá tehessük az általunk nyújtott szolgáltatást. Esetenként harmadik féllel is kapcsolatba léphetünk a kinyert használati adatok jobb megértése érdekében. Ezen harmadik felek számára azonban az adatok használata szigorú feltételekhez kötött: kizárólag az engedélyünkkel és királólag a mi szolgáltatásunk fejlesztésével összefüggésben használhatják azokat.</p>
+
+      <h3 id="disclose">Milyen információt adunk ki külső szereplőknek?</h3>
+
+      <p>Soha, semmilyen körülmények között nem adunk ki, át vagy el külső szereplőknek olyan adatot, amelynek segítségével egyes felhasználóink egyedileg azonosíthatók. Ez nem vonatkozik olyan harmadik felekre, melyek jelen szolgáltatás üzemeltetésében, javításában vagy támogatásában segítségünkre vannak &ndash; ezeket a feleket azonban titoktartási szerződés köti mondott adatokkal kapcsolatban. A gyűjtött adatokat ezen felül megfelelő meghagyás megléte esetén kiadhatjuk a törvény és a rendfenntartás képviselőinek, amennyiben ezen adatoknak jog-, élet- vagy vagyonvédelmi jelentőségük van. Hirdetési- és marketing-, valamint egyéb, a fentiekben nem érintett célból csak olyan adatok adhatók ki, amelyek nem teszik lehetővé az egyes felhasználók egyedi azonosítását.</p>
+
+      <h3 id="third-party">Harmadik felekre mutató hivatkozások</h3>
+
+      <p>Esetenként elhelyezhetünk harmadik fél által ajánlott termékekre vagy szolgáltatásokra mutató hivatkozásokat az oldalon. Ezen harmadik feleknek saját, tőlünk független adatvédelmi és adatkezelési nyilatkozatuk van. Ennek értelmében az oldal üzemeltetői semmilyen felelősséget nem tudnak vállalni az ezen harmadik fél által üzemeltetett oldalak viselkedésével és tartalmával kapcsolatban. Ugyanakkor arra törekszünk, hogy mindenben saját felhasználóink érdekeit képviseljük, így minden, a fenti harmadik felekkel kacsolatos visszejelzést szívesen veszünk.</p>
+
+      <h3 id="coppa">Megfelelés a Gyermekek Online Adatvédelméről Szóló Rendeletnek</h3>
+
+      <p>Az oldal, valamint az azon keresztül nyújtott szolgáltatás a 13 éven felülieket célozza. Amennyiben ez a szerver az Amerikai Egyesült Államok területén található és te nem vagy még 13 éves, a COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Gyermekek Online Adatvédelméről Szóló Rendelet</a>) értelmében kérjük ne használd ezt az oldalt és szolgáltatást.
+
+      <h3 id="online">Az adatvédelmi és adatkezelési nyilatkozat hatálya</h3>
+
+      <p>Jelen adatvédelmi és adatkezelési nyilatkozat kizárólag az oldalunkon keresztül gyűjtött online adatokra vonatkozik, offline módon gyűjtött adatokra nem terjed ki.</p>
+
+      <h3 id="consent">Használói beleegyezés</h3>
+
+      <p>Az oldal és a szolgáltatás használatával elfogadottnak tekinted jelen adatvédelmi és adatkezelési nyilatkozatot.</p>
+
+      <h3 id="changes">A nyilatkozat módosításairól</h3>
+
+      <p>Amennyiben a jövőben módosítjuk jelen adatvédelmi és adatkezelési nyilatkozatunkat, a módosított szöveg ugyanezen oldalon lesz megtalálható.</p>
+
+      <p>Jelen dokumentum a CC-BY-SA licenc alatt érhető el. Angol eredetijének utolsó módosítása: 2013. május 31.</p>
+
+      <p>A dokumetum a <a href="https://github.com/discourse/discourse">Discourse adatvédelmi és adatkezelési nyilatkozatán</a> alapul.</p>
+    title: "%{instance} Felhasználási feltételek és Adatkezelési nyilatkozat"
+  themes:
+    default: Mastodon
+  time:
+    formats:
+      default: "%Y %b %d, %H:%M"
+  two_factor_authentication:
+    code_hint: Megerősítéshez írd be az alkalmazás által generált kódot
+    description_html: He engedélyezed a <strong>kétlépcsős azonosítást</strong>, a bejelentkezéshez szükséged lesz a teefonodre és egy alkalmazásra, amely hozzáférési kódot generál számodra.
+    disable: Kikapcsolás
+    enable: Engedélyezés
+    enabled: Kétlépcsős azonosítás engedélyezve
+    enabled_success: A kétlépcsős azonosítást sikeresen engedélyezted
+    generate_recovery_codes: Visszaállítási kódok generálása
+    instructions_html: "<strong>Olvasd be ez a QR-kódot a telefonodon futó Google Authenticator (vagy egyéb TOTP) alkalmazással</strong>. A jövőben ez az alkalmazás fog számodra hozzáférési kódot generálni a belépéshez."
+    lost_recovery_codes: A visszaállítási kódok segítségével tudsz belépni, ha elveszítenéd a telefonod. Ha a visszaállítási kódjaidat hagytad el, itt generálhatsz újakat. A régi kódokat ebben az esetben érvénytelenítjük.
+    manual_instructions: 'Ha nem sikerült a QR-kód beolvasása, itt a szöveges kulcs, amelyet manuálisan kell begépelned:'
+    recovery_codes: Visszaállítási kódok biztonsági mentése
+    recovery_codes_regenerated: Visszaállítási kódok sikeresen újragenerálva
+    recovery_instructions_html: A visszaállítási kódok egyikének segítségével tudsz majd belépni, ha elveszítenéd a telefonod. <strong>Tartsd biztos helyen a visszaállítási kódjaid</strong>! Például nyomtasd ki őket és tárold a többi fontos iratoddal együtt.
+    setup: Beállítás
+    wrong_code: A beírt kód nem érvényes! A szerver órája és az eszközöd órája szinkronban jár?
+  user_mailer:
+    welcome:
+      edit_profile_action: Készítsd el profilod
+      edit_profile_step: 'Itt tudod egyedivé tenni a profilod: feltölthetsz profil- és borítóképet, megváltoztathatod a megjelenített neved és így tovább. Ha jóvá szeretnéd hagyni követőidet, mielőtt láthatják a tülkjeid, itt tudod a fiókodat zárttá tenni.'
+      explanation: Néhány tipp a kezdeti lépésekhez
+      final_action: Kezdj tülkölni
+      final_step: 'Kezdj tülkölni! Publikus üzeneteid még követők híján is megjelennek másoknak, például a helyi időfolyamban és a címkéknél. Kezdd például azzal, hogy bemutatkozol: használd a #bemutatkozas és az #introductions címkét a tülködben.'
+      full_handle: Teljes felhasználóneved
+      full_handle_hint: Ez az, amit megadhatsz másoknak, hogy üzenhessenek neked vagy követhessenek téged más instanciákról.
+      review_preferences_action: Beállítások módosítása
+      review_preferences_step: Tekintsd át beállításaidat, például hogy milyen értesítéseket kérsz emailben vagy hogy alapértelmezettként mi legyen a tülkjeid adatvédelmi beállítása. Ha nem vagy szédülős alkat, azt is engedélyezheted, hogy automatikusan lejátsszuk a GIF-eket.
+      subject: Üdvözöl a Mastodon
+      tip_bridge_html: Ha a Twitterről érkezel, használhatod <a href="%{bridge_url}">alkalmazásunkat</a>, amellyel megtalálhatod Twitteres barátaidat a Mastodonon. Az alkalmazás csak azon barátaidat tudja megtalálni, akik maguk is használták azt!
+      tip_federated_timeline: A nyilvános időfolyam a Mastodon ütőere, ahol minden tülk összefolyik. Nem teljes ugyan, mert csak azokat az emberek fogod látni, akiket instanciád többi felhasználója követ.
+      tip_following: Alapértelmezettként instanciád adminisztrátorait követed. Látogasd meg a helyi és a nyilvános időfolyamot, hogy más érdekes emberekre is rátalálj.
+      tip_local_timeline: A helyi időfolyam a saját instanciád (%{instance}) ütőere. Ezek a kedves emberek itt mind a szomszédaid!
+      tip_mobile_webapp: Ha a böngésződ lehetővé teszi, hogy kezdőképernyődhöz add a Mastodont, még értesítéseket is fogsz kapni &ndash; akárcsak egy igazi alkalmazás esetében!
+      tips: Tippek
+      title: Üdv a fedélzeten, %{name}!
+  users:
+    invalid_email: A megadott e-mail cím helytelen
+    invalid_otp_token: Érvénytelen ellenőrző kód
+    signed_in_as: Bejelentkezve mint
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 6e4d60fd8..0ef1d5040 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -155,7 +155,6 @@ id:
   applications:
     invalid_url: URL tidak sesuai
   auth:
-    change_password: Identitas
     didnt_get_confirmation: Tidak menerima petunjuk konfirmasi?
     forgot_password: Lupa kata sandi?
     login: Masuk
@@ -163,6 +162,7 @@ id:
     register: Daftar
     resend_confirmation: Kirim ulang email konfirmasi
     reset_password: Reset kata sandi
+    security: Identitas
     set_new_password: Tentukan kata sandi baru
   authorize_follow:
     error: Sayangnya, ada error saat melihat akun remote
diff --git a/config/locales/io.yml b/config/locales/io.yml
index db8214768..29ab4516b 100644
--- a/config/locales/io.yml
+++ b/config/locales/io.yml
@@ -153,7 +153,6 @@ io:
   applications:
     invalid_url: La URL donita ne esas valida
   auth:
-    change_password: Chanjar pasvorto
     didnt_get_confirmation: Ka tu ne recevis la instrucioni por konfirmar?
     forgot_password: Pasvorto obliviita?
     login: Enirar
@@ -161,6 +160,7 @@ io:
     register: Membreskar
     resend_confirmation: Risendar la instrucioni por konfirmar
     reset_password: Chanjar la pasvorto
+    security: Chanjar pasvorto
     set_new_password: Selektar nova pasvorto
   authorize_follow:
     error: Regretinde, eventis eraro probante konsultar la fora konto
diff --git a/config/locales/it.yml b/config/locales/it.yml
index 6ab57d2fc..7e5bfd20e 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -30,7 +30,6 @@ it:
   applications:
     invalid_url: L'URL fornito non è valido
   auth:
-    change_password: Credenziali
     didnt_get_confirmation: Non hai ricevuto le istruzioni di conferma?
     forgot_password: Hai dimenticato la tua password?
     login: Entra
@@ -38,6 +37,7 @@ it:
     register: Iscriviti
     resend_confirmation: Invia di nuovo le istruzioni di conferma
     reset_password: Resetta la password
+    security: Credenziali
     set_new_password: Imposta una nuova password
   authorize_follow:
     error: Sfortunatamente c'è stato un errore nel consultare l'account remoto
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 141b5141a..50047bca2 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -23,7 +23,6 @@ ja:
       real_conversation_title: 本当のコミュニケーションのために
       within_reach_body: デベロッパーフレンドリーな API により実現された、iOS や Android、その他様々なプラットフォームのためのアプリでどこでも友人とやりとりできます。
       within_reach_title: いつでも身近に
-    find_another_instance: 他のインスタンスを探す
     generic_description: "%{domain} は、Mastodon インスタンスの一つです"
     hosted_on: Mastodon hosted on %{domain}
     learn_more: もっと詳しく
@@ -266,7 +265,7 @@ ja:
       view: 表示
     settings:
       activity_api_enabled:
-        desc_html: ローカルに投稿されたトゥート数、アクティブなユーザー数、週ごとの新規登録者数
+        desc_html: 週ごとのローカルに投稿されたトゥート数、アクティブなユーザー数、新規登録者数
         title: ユーザーアクティビティに関する統計を公開する
       bootstrap_timeline_accounts:
         desc_html: 複数のユーザー名はコンマで区切ります。ローカルの公開アカウントのみ有効です。指定しない場合は管理者がデフォルトで指定されます。
@@ -274,6 +273,9 @@ ja:
       contact_information:
         email: ビジネスメールアドレス
         username: 連絡先のユーザー名
+      hero:
+        desc_html: フロントページに表示されます。サイズは600x100px以上推奨です。未設定の場合、インスタンスのサムネイルが使用されます
+        title: ヒーローイメージ
       peers_api_enabled:
         desc_html: 連合内でこのインスタンスが遭遇したドメインの名前
         title: 接続しているインスタンスのリストを公開する
@@ -290,6 +292,9 @@ ja:
         open:
           desc_html: 誰でも自由にアカウントを作成できるようにします
           title: 新規登録を受け付ける
+      show_known_fediverse_at_about_page:
+        desc_html: チェックを入れるとプレビュー欄に既知の連合先全てのトゥートを表示します。外すとローカルのトゥートだけ表示します。
+        title: タイムラインプレビューに連合タイムラインを表示する
       show_staff_badge:
         desc_html: ユーザーページにスタッフのバッジを表示します
         title: スタッフバッジを表示する
@@ -350,11 +355,12 @@ ja:
     invalid_url: URLが無効です
     regenerate_token: アクセストークンの再生成
     token_regenerated: アクセストークンが再生成されました
-    warning: このデータは気をつけて取り扱ってください。不特定多数の人と共有しないでください!
+    warning: このデータは気をつけて取り扱ってください。他の人と共有しないでください!
     your_token: アクセストークン
   auth:
     agreement_html: 登録すると <a href="%{rules_path}">インスタンスのルール</a> と <a href="%{terms_path}">利用規約</a> に従うことに同意したことになります。
-    change_password: セキュリティ
+    change_password: パスワード
+    confirm_email: メールアドレスの確認
     delete_account: アカウントの削除
     delete_account_html: アカウントを削除したい場合、<a href="%{path}">こちら</a> から手続きが行えます。削除する前に、確認画面があります。
     didnt_get_confirmation: 確認メールを受信できませんか?
@@ -364,9 +370,16 @@ ja:
     logout: ログアウト
     migrate_account: 別のアカウントに引っ越す
     migrate_account_html: 引っ越し先を明記したい場合は<a href="%{path}">こちら</a>で設定できます。
+    or: または
+    or_log_in_with: または次のサービスでログイン
+    providers:
+      cas: CAS
+      saml: SAML
     register: 登録する
+    register_elsewhere: 他のインスタンスで新規登録
     resend_confirmation: 確認メールを再送する
     reset_password: パスワードを再発行
+    security: セキュリティ
     set_new_password: 新しいパスワード
   authorize_follow:
     error: 残念ながら、リモートアカウント情報の取得中にエラーが発生しました
@@ -413,6 +426,13 @@ ja:
       title: このページは正しくありません
     noscript_html: Mastodonのウェブアプリケーションを利用する場合はJavaScriptを有効にしてください。またはあなたのプラットフォーム向けの<a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">Mastodonネイティブアプリ</a>を探すことができます。
   exports:
+    archive_takeout:
+      date: 日時
+      download: ダウンロード
+      hint_html: "<strong>トゥートとメディア</strong>のアーカイブをリクエストできます。 データはActivityPub形式で、対応しているソフトウェアで読み込むことができます。"
+      in_progress: 準備中...
+      request: アーカイブをリクエスト
+      size: 容量
     blocks: ブロック
     csv: CSV
     follows: フォロー
@@ -422,7 +442,7 @@ ja:
     domain: ドメイン
     explanation_html: あなたの投稿のプライバシーを確保したい場合、誰があなたをフォローしているのかを把握している必要があります。 <strong>プライベート投稿は、あなたのフォロワーがいる全てのインスタンスに配信されます</strong>。 フォロワーのインスタンスの管理者やソフトウェアがあなたのプライバシーを尊重してくれるかどうか怪しい場合は、そのフォロワーを削除した方がよいかもしれません。
     followers_count: フォロワー数
-    lock_link: 非公開アカウント
+    lock_link: 非公開アカウントにする
     purge: フォロワーから削除する
     success:
       one: 1個のドメインからソフトブロックするフォロワーを処理中...
@@ -494,7 +514,7 @@ ja:
   notification_mailer:
     digest:
       action: 全ての通知を表示
-      body: "%{instance} での最後のログインからの出来事:"
+      body: '最後のログイン(%{since})からの出来事:'
       mention: "%{name} さんがあなたに返信しました:"
       new_followers_summary:
         one: また、離れている間に新たなフォロワーを獲得しました!
@@ -537,7 +557,9 @@ ja:
           trillion: T
           unit: ''
   pagination:
+    newer: 新しいトゥート
     next: 次
+    older: 以前のトゥート
     prev: 前
     truncate: "&hellip;"
   preferences:
@@ -572,12 +594,14 @@ ja:
       blackberry: Blackberry
       chrome: Chrome
       edge: Microsoft Edge
+      electron: Electron
       firefox: Firefox
       generic: 不明なブラウザ
       ie: Internet Explorer
       micro_messenger: MicroMessenger
       nokia: Nokia S40 Ovi Browser
       opera: Opera
+      otter: Otter
       phantom_js: PhantomJS
       qq: QQ Browser
       safari: Safari
@@ -613,6 +637,7 @@ ja:
     flavours: フレーバー
     followers: 信頼済みのインスタンス
     import: データのインポート
+    keyword_mutes: ミュートされたキーワード
     migrate: アカウントの引っ越し
     notifications: 通知
     preferences: ユーザー設定
@@ -639,7 +664,7 @@ ja:
   stream_entries:
     click_to_show: クリックして表示
     pinned: 固定されたトゥート
-    reblogged: さんにブーストされました
+    reblogged: さんがブースト
     sensitive_content: 閲覧注意
   terms:
     body_html: |
@@ -711,6 +736,8 @@ ja:
 
       <p>オリジナルの出典 <a href="https://github.com/discourse/discourse">Discourse privacy policy</a>.</p>
     title: "%{instance} 利用規約・プライバシーポリシー"
+  themes:
+    default: Mastodon
   time:
     formats:
       default: "%Y年%m月%d日 %H:%M"
@@ -730,7 +757,31 @@ ja:
     recovery_instructions_html: 携帯電話を紛失した場合、以下の内どれかのリカバリーコードを使用してアカウントへアクセスすることができます。<strong>リカバリーコードは大切に保全してください。</strong>たとえば印刷してほかの重要な書類と一緒に保管することができます。
     setup: 初期設定
     wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。
+  user_mailer:
+    backup_ready:
+      explanation: Mastodonアカウントのアーカイブを受け付けました。今すぐダウンロードできます!
+      subject: アーカイブの準備ができました
+      title: アーカイブの取り出し
+    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> を使用することで Mastodon での友達のアカウントを探すこともできます。ただし bridge app を使用したことのある相手に限ります!
+      tip_federated_timeline: 連合タイムラインは Mastodon ネットワークの流れを見られるものです。ただしあなたと同じインスタンスの人がフォローしている人だけが含まれるので、それが全てではありません。
+      tip_following: 標準では自動でインスタンスの管理者をフォローしています。もっと興味のある人たちを見つけるには、ローカルタイムラインと連合タイムラインを確認してください。
+      tip_local_timeline: ローカルタイムラインは %{instance} にいる人々の流れを見られるものです。彼らはあなたと同じインスタンスにいる隣人のようなものです!
+      tip_mobile_webapp: もしモバイル端末のブラウザで Mastodon をホーム画面に追加できる場合、プッシュ通知を受け取ることができます。それはまるでネイティブアプリのように動作します!
+      tips: 豆知識
+      title: ようこそ、%{name} !
   users:
     invalid_email: メールアドレスが無効です
     invalid_otp_token: 二段階認証コードが間違っています
+    seamless_external_login: あなたは外部サービスを介してログインしているため、パスワードとメールアドレスの設定は利用できません。
     signed_in_as: '下記でログイン中:'
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index b254636f3..249cb7bf1 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -23,9 +23,8 @@ ko:
       real_conversation_title: 진정한 커뮤니케이션을 위하여
       within_reach_body: 개발자 친화적인 API에 의해서 실현된 iOS나 Android, 그 외의 여러 Platform들 덕분에 어디서든 친구들과 자유롭게 메세지를 주고 받을 수 있습니다.
       within_reach_title: 언제나 유저의 곁에서
-    find_another_instance: 다른 인스턴스 찾기
     generic_description: "%{domain} 은 Mastodon의 인스턴스 입니다."
-    hosted_on: Mastodon hosted on %{domain}
+    hosted_on: "%{domain}에서 호스팅 되는 마스토돈"
     learn_more: 자세히
     other_instances: 다른 인스턴스
     source_code: 소스 코드
@@ -40,13 +39,13 @@ ko:
     following: 팔로잉
     media: 미디어
     moved_html: "%{name}은 %{new_profile_link}으로 이동되었습니다:"
-    nothing_here: 아무 것도 없습니다.
+    nothing_here: 아무 것도 없습니다!
     people_followed_by: "%{name} 님이 팔로우 중인 계정"
     people_who_follow: "%{name} 님을 팔로우 중인 계정"
     posts: 툿
     posts_with_replies: 툿과 답장
     remote_follow: 리모트 팔로우
-    reserved_username: 이 아이디는 예약되어 있습니다.
+    reserved_username: 이 아이디는 예약되어 있습니다
     roles:
       admin: 관리자
       moderator: 모더레이터
@@ -56,9 +55,9 @@ ko:
       account: 모더레이터
       create: 작성하기
       created_at: 작성 날짜
-      created_msg: 모더레이션 기록이 성공적으로 작성되었습니다.
+      created_msg: 모더레이션 기록이 성공적으로 작성되었습니다!
       delete: 삭제
-      destroyed_msg: 모더레이션 기록이 성공적으로 삭제되었습니다.
+      destroyed_msg: 모더레이션 기록이 성공적으로 삭제되었습니다!
     accounts:
       are_you_sure: 정말로 실행하시겠습니까?
       by_domain: 도메인
@@ -78,7 +77,7 @@ ko:
       followers: 팔로워 수
       followers_url: 팔로워 URL
       follows: 팔로잉 수
-      inbox_url: Inbox URL
+      inbox_url: 수신함 URL
       ip: IP
       location:
         all: 전체
@@ -101,7 +100,7 @@ ko:
         alphabetic: 알파벳 순
         most_recent: 최근 활동 순
         title: 순서
-      outbox_url: Outbox URL
+      outbox_url: 발신함 URL
       perform_full_suspension: 완전히 정지시키기
       profile_url: 프로필 URL
       promote: 모더레이터로 승급
@@ -154,7 +153,7 @@ ko:
         reset_password_user: "%{name}이 %{target}의 암호를 초기화했습니다"
         resolve_report: "%{name}이 %{target} 신고를 처리됨으로 변경하였습니다"
         silence_account: "%{name}이 %{target}의 계정을 뮤트시켰습니다"
-        suspend_account: "%{name}이 %{target}의 계정을 정지시켰습니다 "
+        suspend_account: "%{name}이 %{target}의 계정을 정지시켰습니다"
         unsilence_account: "%{name}이 %{target}에 대한 뮤트를 해제했습니다"
         unsuspend_account: "%{name}이 %{target}에 대한 정지를 해제했습니다"
         update_custom_emoji: "%{name}이 에모지 %{target}를 업데이트 했습니다"
@@ -187,20 +186,22 @@ ko:
       upload: 업로드
     domain_blocks:
       add_new: 추가하기
-      created_msg: 도메인 차단 처리를 완료했습니다.
-      destroyed_msg: 도메인 차단을 해제했습니다.
+      created_msg: 도메인 차단 처리를 완료했습니다
+      destroyed_msg: 도메인 차단이 해제되었습니다
       domain: 도메인
       new:
         create: 차단 추가
         hint: 도메인 차단은 내부 데이터베이스에 계정이 생성되는 것까지는 막을 수 없지만, 그 도메인에서 생성된 계정에 자동적으로 특정한 모더레이션을 적용하게 할 수 있습니다.
         severity:
-          desc_html: "<strong>침묵</strong>은 계정을 팔로우 하지 않고 있는 사람들에겐 계정의 툿을 보이지 않게 합니다. <strong>정지</strong>는 계정의 컨텐츠, 미디어, 프로필 데이터를 삭제합니다."
+          desc_html: |-
+            <strong>침묵</strong>은 계정을 팔로우 하지 않고 있는 사람들에겐 계정의 툿을 보이지 않게 합니다. <strong>정지</strong>는 계정의 컨텐츠, 미디어, 프로필 데이터를 삭제합니다.
+            미디어 파일만을 거부하고 싶다면 <strong>없음</strong>으로 두세요.
           noop: 없음
           silence: 침묵
           suspend: 정지
         title: 새로운 도메인 차단
       reject_media: 미디어 파일 거부하기
-      reject_media_hint: 로컬에 저장된 미디어 파일을 삭제하고, 이후로도 다운로드를 거부합니다. 정지하고는 관계 없습니다.
+      reject_media_hint: 로컬에 저장된 미디어 파일을 삭제하고, 이후로도 다운로드를 거부합니다. 정지와는 관계 없습니다
       severities:
         noop: 없음
         silence: 침묵
@@ -274,40 +275,46 @@ ko:
       contact_information:
         email: 공개할 메일 주소를 입력
         username: 아이디를 입력
+      hero:
+        desc_html: 프론트페이지에 표시 됩니다. 최소 600x100픽셀을 권장합니다. 만약 설정되지 않았다면, 인스턴스의 썸네일이 사용 됩니다
+        title: 히어로 이미지
       peers_api_enabled:
         desc_html: 이 인스턴스가 페디버스에서 만났던 도메인 네임들
         title: 발견 된 인스턴스들의 리스트 발행
       registrations:
         closed_message:
-          desc_html: 신규 등록을 받지 않을 때 프론트 페이지에 표시됩니다. <br>HTML 태그를 사용할 수 있습니다.
+          desc_html: 신규 등록을 받지 않을 때 프론트 페이지에 표시됩니다. HTML 태그를 사용할 수 있습니다
           title: 신규 등록 정지 시 메시지
         deletion:
-          desc_html: 유저가 자신의 계정을 삭제할 수 있도록 설정합니다.
+          desc_html: 유저가 자신의 계정을 삭제할 수 있도록 설정합니다
           title: 계정 삭제를 허가함
         min_invite_role:
           disabled: 아무도 못 하게
           title: 초대링크를 만들 수 있는 권한
         open:
-          desc_html: 유저가 자신의 계정을 생성할 수 있도록 설정합니다.
+          desc_html: 계정을 생성할 수 있도록 허용합니다
           title: 신규 계정 등록을 받음
+      show_known_fediverse_at_about_page:
+        desc_html: 활성화 되면 프리뷰 페이지에서 페디버스의 모든 툿을 표시합니다. 비활성화시 로컬에 있는 툿만 표시 됩니다.
+        title: 타임라인 프리뷰에 알려진 페디버스 표시하기
       show_staff_badge:
-        desc_html: 유저 페이지에 스태프 배지를 표시합니다.
+        desc_html: 유저 페이지에 스태프 배지를 표시합니다
         title: 스태프 배지 표시
       site_description:
-        desc_html: 탑 페이지와 meta 태그에 사용됩니다.<br>HTML 태그, 예를 들어<code>&lt;a&gt;</code> 태그와 <code>&lt;em&gt;</code> 태그를 사용할 수 있습니다.
+        desc_html: 탑 페이지와 meta 태그에 사용됩니다. HTML 태그, 주로 <code>&lt;a&gt;</code>, <code>&lt;em&gt;</code> 같은 것을 사용 가능합니다.
         title: 사이트 설명
       site_description_extended:
-        desc_html: 인스턴스 정보 페이지에 표시됩니다.<br>HTML 태그를 사용할 수 있습니다.
+        desc_html: 규칙, 가이드라인 등을 작성하기 좋은 곳입니다. HTML 태그를 사용할 수 있습니다
         title: 사이트 상세 설명
       site_terms:
-        desc_html: 당신은 독자적인 개인정보 취급 방침이나 이용약관, 그 외의 법적 근거를 작성할 수 있습니다. 또한 HTML태그를 사용할 수 있습니다.
+        desc_html: 당신은 독자적인 개인정보 취급 방침이나 이용약관, 그 외의 법적 근거를 작성할 수 있습니다. HTML태그를 사용할 수 있습니다
         title: 커스텀 서비스 이용 약관
       site_title: 사이트 이름
       thumbnail:
         desc_html: OpenGraph와 API의 미리보기로 사용 됩니다. 1200x630px을 권장합니다
         title: 인스턴스 썸네일
       timeline_preview:
-        desc_html: Landing page에 공개 타임라인을 표시합니다.
+        desc_html: 랜딩 페이지에 공개 타임라인을 표시합니다
         title: 타임라인 프리뷰
       title: 사이트 설정
     statuses:
@@ -317,7 +324,7 @@ ko:
         nsfw_off: NSFW 끄기
         nsfw_on: NSFW 켜기
       execute: 실행
-      failed_to_execute: 실행이 실패하였습니다.
+      failed_to_execute: 실행을 실패하였습니다
       media:
         hide: 미디어 숨기기
         show: 미디어 보여주기
@@ -339,21 +346,22 @@ ko:
       subject: "%{instance} 에 새 신고 등록됨 (#%{id})"
   application_mailer:
     notification_preferences: 메일 설정 변경
+    salutation: "%{name} 님,"
     settings: '메일 설정을 변경: %{link}'
     view: 'View:'
     view_profile: 프로필 보기
     view_status: 게시물 보기
   applications:
-    created: 애플리케이션이 작성되었습니다.
-    destroyed: 애플리케이션이 삭제되었습니다.
+    created: 애플리케이션이 성공적으로 생성되었습니다
+    destroyed: 애플리케이션이 성공적으로 삭제되었습니다
     invalid_url: 올바르지 않은 URL입니다
     regenerate_token: 토큰 재생성
-    token_regenerated: 액세스 토큰이 재생성되었습니다.
-    warning: 이 데이터는 다른 사람들과 절대로 공유하지 마세요.
+    token_regenerated: 액세스 토큰이 성공적으로 재생성되었습니다
+    warning: 이 데이터를 조심히 다뤄 주세요. 다른 사람들과 절대로 공유하지 마세요!
     your_token: 액세스 토큰
   auth:
-    agreement_html: 이 등록으로 <a href="%{rules_path}">이용규약</a> 과 <a href="%{terms_path}">개인정보 취급 방침</a>에 동의하는 것으로 간주됩니다.
-    change_password: 보안
+    agreement_html: 이 등록으로 <a href="%{rules_path}">이용규약</a> 과 <a href="%{terms_path}">약관</a>에 동의하는 것으로 간주됩니다.
+    confirm_email: 확인 메일 승인
     delete_account: 계정 삭제
     delete_account_html: 계정을 삭제하고 싶은 경우, <a href="%{path}">여기서</a> 삭제할 수 있습니다. 삭제 전 확인 화면이 표시됩니다.
     didnt_get_confirmation: 확인 메일을 받지 못하셨습니까?
@@ -363,17 +371,20 @@ ko:
     logout: 로그아웃
     migrate_account: 계정 옮기기
     migrate_account_html: 이 계정을 다른 계정으로 리디렉션 하길 원하는 경우 <a href="%{path}">여기</a>에서 설정할 수 있습니다.
+    or: 또는
+    or_log_in_with: 다른 방법으로 로그인 하려면
     register: 등록하기
     resend_confirmation: 확인 메일을 다시 보내기
     reset_password: 비밀번호 재설정
+    security: 보안
     set_new_password: 새 비밀번호
   authorize_follow:
-    error: 리모트 팔로우 도중 오류가 발생했습니다.
+    error: 리모트 계정을 확인하는 도중 오류가 발생했습니다
     follow: 팔로우
     follow_request: '당신은 다음 계정에 팔로우 신청을 했습니다:'
     following: '성공! 당신은 다음 계정을 팔로우 하고 있습니다:'
     post_follow:
-      close: 혹은, 당신은 이 윈도우를 닫을 수 있습니다
+      close: 혹은, 당신은 이 윈도우를 닫을 수 있습니다.
       return: 유저 프로필로 돌아가기
       web: 웹으로 가기
     title: "%{acct} 를 팔로우"
@@ -393,25 +404,32 @@ ko:
       x_seconds: "%{count}초"
   deletes:
     bad_password_msg: 비밀번호가 올바르지 않습니다
-    confirm_password: 본인 확인을 위해, 현재 사용 중인 비밀번호를 입력해 주십시오.
+    confirm_password: 본인 확인을 위해 현재 사용 중인 암호를 입력해 주십시오
     description_html: 계정에 업로드된 모든 컨텐츠가 삭제되며, 계정은 비활성화 됩니다. 이것은 영구적으로 이루어지는 것이므로 <strong>되돌릴 수 없습니다</strong>. 사칭 행위를 방지하기 위해 같은 아이디로 다시 등록하는 것은 불가능합니다.
     proceed: 계정 삭제
-    success_msg: 계정이 정상적으로 삭제되었습니다.
+    success_msg: 계정이 성공적으로 삭제되었습니다
     warning_html: 삭제가 보장되는 것은 이 인스턴스 상에서의 컨텐츠에 한합니다. 타 인스턴스 등, 외부에 멀리 공유된 컨텐츠는 흔적이 남아 삭제되지 않는 경우도 있습니다. 그리고 현재 접속이 불가능한 서버나, 업데이트를 받지 않게 된 서버에 대해서는 삭제가 반영되지 않을 수도 있습니다.
     warning_title: 공유된 컨텐츠에 대해서
   errors:
-    '403': 이 페이지를 표시할 권한이 없습니다
-    '404': 페이지를 찾을 수 없습니다
-    '410': 이 페이지는 더 이상 존재하지 않습니다
+    '403': 이 페이지를 표시할 권한이 없습니다.
+    '404': 당신이 찾으려는 페이지는 존재하지 않습니다.
+    '410': 당신이 보려는 페이지는 더이상 존재하지 않습니다.
     '422':
       content: 보안 인증에 실패했습니다. Cookie를 차단하고 있진 않습니까?
       title: 보안 인증 실패
-    '429': 요청 횟수 제한에 도달했습니다.
+    '429': 요청 횟수 제한에 도달했습니다
     '500':
       content: 죄송합니다, 뭔가 잘못 되었습니다.
-      title: 이 페이지는 잘못되었습니다.
-    noscript_html: Mastodon을 사용하기 위해서는 JavaScript를 켜 주십시오.
+      title: 이 페이지는 잘못되었습니다
+    noscript_html: 마스토돈을 사용하기 위해서는 자바스크립트를 켜 주십시오. 아니면 <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">네이티브 앱</a> 중 하나를 사용할 수 있습니다.
   exports:
+    archive_takeout:
+      date: 날짜
+      download: 아카이브 다운로드
+      hint_html: 당신의 <strong>툿과 업로드 된 미디어</strong>의 아카이브를 요청할 수 있습니다. 내보내지는 데이터는 ActivityPub 포맷입니다. 호환 되는 모든 소프트웨어에서 읽을 수 있습니다.
+      in_progress: 당신의 아카이브를 컴파일 중입니다…
+      request: 아카이브 요청하기
+      size: 크기
     blocks: 차단
     csv: CSV
     follows: 팔로우
@@ -428,17 +446,17 @@ ko:
       other: "%{count}개 도메인에서 팔로워를 soft-block 처리 중..."
     true_privacy_html: "<strong>프라이버시 보호는 End-to-End 암호화로만 이루어 질 수 있다는 것에 유의</strong>해 주십시오."
     unlocked_warning_html: 누구든 여러분을 팔로우 할 수 있으며, 여러분의 프라이빗 투고를 볼 수 있습니다. 팔로우 할 수 있는 사람을 제한하고 싶은 경우 %{lock_link}에서 설정해 주십시오.
-    unlocked_warning_title: 이 계정은 비공개로 설정되어 있지 않습니다.
+    unlocked_warning_title: 이 계정은 비공개로 설정되어 있지 않습니다
   generic:
-    changes_saved_msg: 정상적으로 변경되었습니다.
-    powered_by: powered by %{link}
+    changes_saved_msg: 정상적으로 변경되었습니다!
+    powered_by: "%{link}에 의해 제공"
     save_changes: 변경 사항을 저장
     validation_errors:
       one: 오류가 발생했습니다. 아래 오류를 확인해 주십시오
       other: 오류가 발생했습니다. 아래 %{count}개 오류를 확인해 주십시오
   imports:
     preface: 다른 인스턴스에서 내보내기 한 파일에서 팔로우 / 차단 정보를 이 인스턴스 계정으로 불러올 수 있습니다.
-    success: 파일이 정상적으로 업로드 되었으며, 현재 처리 중입니다. 잠시 후 다시 확인해 주십시오.
+    success: 파일이 정상적으로 업로드 되었으며, 현재 처리 중입니다
     types:
       blocking: 차단한 계정 목록
       following: 팔로우 중인 계정 목록
@@ -472,8 +490,8 @@ ko:
       limit: 리스트 최대치에 도달했습니다
   media_attachments:
     validations:
-      images_and_video: 이미 사진이 첨부되어 있으므로 동영상을 첨부할 수 없습니다.
-      too_many: 최대 4개까지 첨부할 수 있습니다.
+      images_and_video: 이미 사진이 첨부 된 게시물엔 동영상을 첨부 할 수 없습니다
+      too_many: 최대 4개까지 첨부할 수 있습니다
   migrations:
     acct: 새 계정의 username@domain
     currently_redirecting: '당신의 프로파일은 여기로 리디렉션 됩니다:'
@@ -484,26 +502,26 @@ ko:
   notification_mailer:
     digest:
       action: 모든 알림 보기
-      body: "%{instance} 에서 마지막 로그인 뒤로 일어난 일:"
+      body: 마지막 로그인(%{since}) 이후로 일어난 일들에 관한 요약
       mention: "%{name} 님이 답장했습니다:"
       new_followers_summary:
-        one: 새 팔로워가 생겼습니다!
-        other: "%{count} 명의 팔로워가 생겼습니다!"
+        one: 그리고, 접속 하지 않으신 동안 새 팔로워가 생겼습니다!
+        other: 게다가, 접속하지 않은 동안 %{count} 명의 팔로워가 생겼습니다!
       subject:
         one: "1건의 새로운 알림 \U0001F418"
         other: "%{count}건의 새로운 알림 \U0001F418"
       title: 당신이 없는 동안에…
     favourite:
-      body: "%{name} 님이 내 툿을 즐겨찾기에 등록했습니다."
+      body: "%{name} 님이 내 툿을 즐겨찾기에 등록했습니다:"
       subject: "%{name} 님이 내 툿을 즐겨찾기에 등록했습니다"
       title: 새 즐겨찾기
     follow:
-      body: "%{name} 님이 나를 팔로우 했습니다"
+      body: "%{name} 님이 나를 팔로우 했습니다!"
       subject: "%{name} 님이 나를 팔로우 했습니다"
       title: 새 팔로워
     follow_request:
       action: 팔로우 요청 관리
-      body: "%{name} 님이 내게 팔로우 요청을 보냈습니다."
+      body: "%{name} 님이 내게 팔로우 요청을 보냈습니다"
       subject: "%{name} 님이 보낸 팔로우 요청"
       title: 새 팔로우 요청
     mention:
@@ -537,9 +555,9 @@ ko:
     web: 웹
   push_notifications:
     favourite:
-      title: "%{name} 님이 당신의 툿를 즐겨찾기에 등록했습니다."
+      title: "%{name} 님이 당신의 툿를 즐겨찾기에 등록했습니다"
     follow:
-      title: "%{name} 님이 나를 팔로우 하고 있습니다."
+      title: "%{name} 님이 나를 팔로우 하고 있습니다"
     group:
       title: "%{count} 건의 알림"
     mention:
@@ -548,12 +566,12 @@ ko:
       action_favourite: 즐겨찾기
       title: "%{name} 님이 답장을 보냈습니다"
     reblog:
-      title: "%{name} 님이 당신의 툿을 부스트 했습니다."
+      title: "%{name} 님이 당신의 툿을 부스트 했습니다"
   remote_follow:
     acct: 아이디@도메인을 입력해 주십시오
     missing_resource: 리디렉션 대상을 찾을 수 없습니다
     proceed: 팔로우 하기
-    prompt: 팔로우 하려 하고 있습니다
+    prompt: '팔로우 하려 하고 있습니다:'
   sessions:
     activity: 마지막 활동
     browser: 브라우저
@@ -562,12 +580,14 @@ ko:
       blackberry: 블랙베리
       chrome: 크롬
       edge: 엣지
+      electron: 일렉트론
       firefox: 파이어폭스
       generic: 알 수 없는 브라우저
       ie: IE
-      micro_messenger: MicroMessenger
+      micro_messenger: 마이크로메신저
       nokia: 노키아 S40 Ovi 브라우저
       opera: 오페라
+      otter: Otter
       phantom_js: PhantomJS
       qq: QQ 브라우저
       safari: 사파리
@@ -591,7 +611,7 @@ ko:
       windows_mobile: 윈도우즈 모바일
       windows_phone: 윈도우즈 폰
     revoke: 삭제
-    revoke_success: 세션이 삭제되었습니다.
+    revoke_success: 세션이 성공적으로 삭제되었습니다
     title: 세션
   settings:
     authorized_apps: 인증된 애플리케이션
@@ -612,10 +632,10 @@ ko:
     open_in_web: Web으로 열기
     over_character_limit: 최대 %{max}자까지 입력할 수 있습니다
     pin_errors:
-      limit: 너무 많은 툿을 고정했습니다.
-      ownership: 다른 사람의 툿은 고정될 수 없습니다.
-      private: 비공개 툿은 고정될 수 없습니다.
-      reblog: 부스트는 고정될 수 없습니다.
+      limit: 이미 너무 많은 툿을 고정했습니다
+      ownership: 다른 사람의 툿은 고정될 수 없습니다
+      private: 비공개 툿은 고정될 수 없습니다
+      reblog: 부스트는 고정될 수 없습니다
     show_more: 더 보기
     title: '%{name}: "%{quote}"'
     visibilities:
@@ -631,6 +651,74 @@ ko:
     reblogged: 님이 부스트 했습니다
     sensitive_content: 민감한 컨텐츠
   terms:
+    body_html: |
+      <h2>사생활 정책</h2>
+
+      <h3 id="collect">우리가 어떤 정보를 수집하나요?</h3>
+
+      <p>우리는 귀하가 우리 사이트에 가입할 때, 그리고 우리의 포럼에 읽고, 쓸 때, 그리고 포럼에 게시된 공유된 콘텐츠를 평가할 때 정보를 수집합니다.</p>
+
+      <p>우리 사이트에 가입할 때, 귀하는 이름과 이메일 주소 입력을 요구 받을 수 있습니다. 하지만 귀하는 우리의 사이트를 가입하지 않고도 방문할 수 있습니다. 귀하의 이메일 주소는 고유한 링크를 담고 있는 이메일로 검증 될 것입니다. 만약 귀하가 그 링크를 방문한다면, 우리는 귀하가 그 이메일 주소를 소유하고 있다는 것을 알 수 있습니다.</p>
+
+      <p>가입을 하고 글을 쓸 때, 우리는 글이 어떤 IP에서 작성 되었는지 기록합니다. 또한 우리는 모든 요청에 대한 IP 주소를 담고 있는 서버 로그를 보관할 수 있습니다.</p>
+
+      <h3 id="use">우리가 귀하의 정보를 어떻게 사용하나요?</h3>
+
+      <p>우리가 귀하에게서 수집하는 어떠한 정보는 다음 중 하나와 같은 방법으로 사용될 수 있습니다:</p>
+
+      <ul>
+        <li>귀하의 경험을 개인화 하기 위해 &mdash; 귀하의 정보는 우리가 귀하의 개별적인 요구에 더 나은 응답을 할 수 있도록 돕습니다.</li>
+        <li>우리의 사이트를 개선하기 위해 &mdash; 우리는 귀하에게 받는 정보와 귀하에게 받는 피드박을 바탕으로 우리의 사이트 내용을 계속 개선하기 위해 노력합니다.</li>
+        <li>고객 서비스를 개선하기 위해 &mdash; 귀하의 정보는 우리가 귀하의 서비스 요청과 지원 요청에 더 효과적으로 응답할 수 있게 돕습니다.</li>
+        <li>주기적인 이메일을 보내기 위해 &mdash; 귀하가 제공하는 이메일 주소는 귀하에게 정보, 귀하가 요청하는 주제에 대한 변경과 귀하의 유저 이름에 대한 응답에 대한 알림, 문의에 대한 답, 또는 다른 요청과 질문을 보내는 데에 사용될 수 있습니다.</li>
+      </ul>
+
+      <h3 id="protect">우리가 어떻게 귀하의 정보를 보호하나요?</h3>
+
+      <p>우리는 귀하가 개인정보를 입력, 제출, 접근 할 때 귀하의 개인정보의 안전을 유지하기 위한 여러가지 보안 방법을 구현합니다.</p>
+
+      <h3 id="data-retention">정보 보관 정책은 어떻게 되나요?</h3>
+
+      <p>우리는 다음과 같이 노력 하겠습니다:</p>
+
+      <ul>
+        <li>모든 요청에 대한 IP 주소를 담고 있는 서버 로그를 최대 90일까지 보관합니다.</li>
+        <li>등록된 사용자와 관련된 IP 주소와 그들의 게시물들을 최대 5년까지 보관합니다.</li>
+      </ul>
+
+      <h3 id="cookies">쿠키를 사용하나요?</h3>
+
+      <p>네. 쿠키는 사이트나 서비스 제공자가 (만약 허용하신다면) 웹 브라우저를 통해 귀하의 컴퓨터 하드디스크에 전송하는 작은 파일들입니다. 이 쿠키들은 사이트가 귀하의 브라우저를 인식하게 하고, 만약 가입한 계정이 있다면 브라우저를 가입한 계정과 연관짓는 일을 가능하게 합니다.</p>
+
+      <p>우리는 쿠키를 사용해 귀하의 환경설정을 미래의 방문을 위해 저장하고, 사이트 접근 기록과 사이트 상호작용 기록을 모아 미래에 우리가 더 나은 사이트 경험과 도구를 제공할 수 있도록 합니다. 우리는 제 3자의 서비스 제공자와 계약하여 우리 사이트의 방문자에 대해 더 나은 이해를 하기 위해 도움을 받을 수 있습니다. 이러한 서비스 제공자들은 우리가 더 나은 서비스를 제공하도록 돕는 목적 외에는 이 정보를 사용할 수 없습니다.</p>
+
+      <h3 id="disclose">우리가 외부에 정보를 공개하나요?</h3>
+
+      <p>우리는 귀하를 식별할 수 있는 정보를 외부에 팔거나, 거래하거나, 전송하지 않습니다. 이는 우리가 우리의 사이트를 운영하고, 사업을 하고, 귀하에게 서비스를 제공하는 데에 도움을 주는 믿을 수 있는 제 3자의 서비스 제공자를 포함하지 않으며, 이는 그 서비스 제공자가 이 정보를 비밀로 취급하는 것에 동의하는지에 따라 다릅니다. 우리는 또한 법을 지키는 것, 우리 사이트의 정책을 집행하는 것, 우리와 다른 사람들의 권리, 재산, 안전을 보호하는 것으로 인해 정보 공개가 적합하다고 생각되면 정보를 공개 할 수 있습니다. 그러나, 귀하를 식별할 수 없는 방문자 정보는 외부에 마케팅, 광고, 혹은 다른 용도로 제공될 수 있습니다.</p>
+
+      <h3 id="third-party">제 3자 링크</h3>
+
+      <p>종종, 우리의 재량에 따라 우리의 사이트에 제 3자의 상품이나 서비스를 포함하거나 제공할 수 있습니다. 이러한 제 3자 사이트는 독립적인 개인정보 정책을 가지고 있습니다. 이러한 링크된 제 3자 사이트의 내용과 활동에 대해서 우리는 어떠한 의무와 법적 책임을 가지고 있지 않습니다. 그래도 우리는 그 사이트에 대한 피드백을 환영하며, 우리 사이트만의 정체성을 유지하도록 노력하겠습니다.</p>
+
+      <h3 id="coppa">아동 온라인 사생활 보호법 준수</h3>
+
+      <p>우리 사이트, 제품과 서비스는 적어도 13살인 사람들에게 맞춰져 있습니다. 만약 이 서버가 미합중국에 위치하고, 귀하가 13살이 되지 않는다면, COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) 의 요구사항에 따라 이 사이트를 이용하지 마십시오.</p>
+
+      <h3 id="online">온라인 사생활 정책 한정</h3>
+
+      <p>이 온라인 사생활 정책은 우리 사이트를 통해 수집된 정보에게만 적용되며, 오프라인에서 수집된 정보에는 적용되지 않습니다.</p>
+
+      <h3 id="consent">귀하의 동의</h3>
+
+      <p>우리의 사이트를 사용함으로서, 귀하는 우리 사이트의 사생활 정책에 동의합니다.</p>
+
+      <h3 id="changes">사생활 정책의 변경</h3>
+
+      <p>만약 우리가 사생활 정책을 변경하도록 결정한다면, 우리는 그 변경사항을 이 페이지에 게시하겠습니다.</p>
+
+      <p>이 문서는 CC-BY-SA 정책으로 배포됩니다. 마지막으로 2013년 3월 31일에 수정되었습니다.</p>
+
+      <p><a href="https://github.com/discourse/discourse">Discourse privacy policy</a>에서 가져옴.</p>
     title: "%{instance} 이용약관과 개인정보 취급 방침"
   time:
     formats:
@@ -645,13 +733,37 @@ ko:
     generate_recovery_codes: 복구 코드 생성
     instructions_html: "<strong>Google Authenticator, 또는 타 TOTP 애플리케이션에서 이 QR 코드를 스캔해 주십시오.</strong> 이후 로그인 시에는 이 애플리케이션에서 생성되는 코드가 필요합니다."
     lost_recovery_codes: 복구 코드를 사용하면 휴대전화를 분실한 경우에도 계정에 접근할 수 있게 됩니다. 복구 코드를 분실한 경우에도 여기서 다시 생성할 수 있지만, 예전 복구 코드는 비활성화 됩니다.
-    manual_instructions: 'QR 코드를 스캔할 수 없어 수동으로 등록을 원하시는 경우 이 비밀 코드를 사용해 주십시오: '
+    manual_instructions: 'QR 코드를 스캔할 수 없어 수동으로 등록을 원하시는 경우 이 비밀 코드를 사용해 주십시오:'
     recovery_codes: 복구 코드
-    recovery_codes_regenerated: 복구 코드가 다시 생성되었습니다.
+    recovery_codes_regenerated: 복구 코드가 다시 생성되었습니다
     recovery_instructions_html: 휴대전화를 분실한 경우, 아래 복구 코드 중 하나를 사용해 계정에 접근할 수 있습니다. <strong>복구 코드는 안전하게 보관해 주십시오.</strong> 이 코드를 인쇄해 중요한 서류와 함께 보관하는 것도 좋습니다.
     setup: 초기 설정
     wrong_code: 코드가 올바르지 않습니다. 서버와 휴대전화 간의 시간이 일치하는지 확인해 주십시오.
+  user_mailer:
+    backup_ready:
+      explanation: 당신이 요청한 계정의 풀 백업이 이제 다운로드 가능합니다.
+      subject: 당신의 아카이브를 다운로드 가능합니다
+      title: 아카이브 테이크 아웃
+    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: 마스토돈에 오신 것을 환영합니다
+      tip_bridge_html: 만약 트위터에서 오셨다면 <a href="%{bridge_url}">브리지 앱</a>을 통해 마스토돈에 있는 친구들을 찾을 수 있습니다. 친구들도 이 앱을 사용했을 때만 작동합니다!
+      tip_federated_timeline: 연합 타임라인은 마스토돈 네트워크의 소방호스입니다. 다만 여기엔 당신의 이웃들이 구독 중인 것만 뜹니다, 모든 것이 다 오는 것은 아니예요.
+      tip_following: 기본적으로 서버의 관리자를 팔로우 하도록 되어 있습니다. 흥미로운 사람들을 더 찾으려면 로컬과 연합 타임라인을 확인해 보세요.
+      tip_local_timeline: 로컬 타임라인은 %{instance}의 소방호스입니다. 여기 있는 사람들은 당신의 이웃들이에요!
+      tip_mobile_webapp: 모바일 브라우저가 홈 스크린에 바로가기를 추가해 줬다면 푸시 알림도 받을 수 있습니다. 이건 거의 네이티브 앱처럼 작동해요!
+      tips: 팁
+      title: 환영합니다 %{name} 님!
   users:
     invalid_email: 메일 주소가 올바르지 않습니다
     invalid_otp_token: 2단계 인증 코드가 올바르지 않습니다
+    seamless_external_login: 외부 서비스를 이용해 로그인 했습니다, 패스워드와 이메일 설정을 할 수 없습니다.
     signed_in_as: '다음과 같이 로그인 중:'
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 973dc65d1..c041fc585 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -23,8 +23,7 @@ nl:
       real_conversation_title: Voor echte gesprekken gemaakt
       within_reach_body: Meerdere apps voor iOS, Android en andere platformen, met dank aan het ontwikkelaarsvriendelijke API-systeem, zorgen ervoor dat je overal op de hoogte blijft.
       within_reach_title: Altijd binnen bereik
-    find_another_instance: Vind een andere server
-    generic_description: "%{domain} is een server in het Mastodon-netwerk"
+    generic_description: "%{domain} is een server in het Mastodonnetwerk"
     hosted_on: Mastodon op %{domain}
     learn_more: Meer leren
     other_instances: Andere servers
@@ -146,7 +145,7 @@ nl:
         destroy_status: Toot van %{target} is door %{name} verwijderd
         disable_2fa_user: Vereisten tweestapsverificatie van %{target} zijn door %{name} uitgeschakeld
         disable_custom_emoji: Emoji %{target} is door %{name} uitgeschakeld
-        disable_user: Inloggen voor %{target} is door %{name} uitgeschakeld
+        disable_user: Aanmelden voor %{target} is door %{name} uitgeschakeld
         enable_custom_emoji: Emoji %{target} is door %{name} ingeschakeld
         enable_user: Inloggen voor %{target} is door %{name} ingeschakeld
         memorialize_account: Account %{target} is door %{name} in een in-memoriampagina veranderd
@@ -274,6 +273,9 @@ nl:
       contact_information:
         email: Vul een openbaar gebruikt e-mailadres in
         username: Vul een gebruikersnaam in
+      hero:
+        desc_html: Wordt op de voorpagina getoond. Tenminste 600x100px aanbevolen. Wanneer dit niet is ingesteld wordt de thumbnail van de Mastodonserver getoond
+        title: Hero-afbeelding
       peers_api_enabled:
         desc_html: Domeinnamen die deze server in de fediverse is tegengekomen
         title: Lijst van bekende servers publiceren
@@ -290,25 +292,28 @@ nl:
         open:
           desc_html: Toestaan dat iedereen een account kan registereren
           title: Open registratie
+      show_known_fediverse_at_about_page:
+        desc_html: Wanneer ingeschakeld wordt de globale tijdlijn als voorbeeld getoond en wanneer uitgeschakeld de lokale tijdljn.
+        title: De globale tijdlijn als voorbeeld tonen
       show_staff_badge:
         desc_html: Medewerkersbadge op profielpagina tonen
         title: Medewerkersbadge tonen
       site_description:
         desc_html: Dit wordt als een alinea op de voorpagina getoond en gebruikt als meta-tag in de paginabron.<br/>Je kan HTML gebruiken, zoals <code>&lt;a&gt;</code> en <code>&lt;em&gt;</code>.
-        title: Omschrijving Mastodon-server
+        title: Omschrijving Mastodonserver
       site_description_extended:
         desc_html: Wordt op de uitgebreide informatiepagina weergegeven<br>Je kan ook hier HTML gebruiken
-        title: Uitgebreide omschrijving Mastodon-server
+        title: Uitgebreide omschrijving Mastodonserver
       site_terms:
         desc_html: Je kan hier jouw eigen privacybeleid, gebruikersvoorwaarden en ander juridisch jargon kwijt. Je kan HTML gebruiken
         title: Aangepaste gebruikersvoorwaarden
-      site_title: Naam Mastodon-server
+      site_title: Naam Mastodonserver
       thumbnail:
         desc_html: Gebruikt als voorvertoning voor OpenGraph en de API. 1200x630px aanbevolen
-        title: Thumbnail Mastodon-server
+        title: Thumbnail Mastodonserver
       timeline_preview:
-        desc_html: Toon de openbare tijdlijn op de startpagina
-        title: Voorbeeld tijdlijn
+        desc_html: Toon een openbare tijdlijn op de voorpagina
+        title: Tijdlijn als voorbeeld tonen
       title: Server-instellingen
     statuses:
       back_to_account: Terug naar accountpagina
@@ -338,9 +343,12 @@ nl:
       body: "%{reporter} heeft %{target} gerapporteerd"
       subject: Nieuwe toots gerapporteerd op %{instance} (#%{id})
   application_mailer:
+    notification_preferences: E-mailvoorkeuren wijzigen
     salutation: "%{name},"
     settings: 'E-mailvoorkeuren wijzigen: %{link}'
     view: 'Bekijk:'
+    view_profile: Profiel bekijken
+    view_status: Status bekijken
   applications:
     created: Aanmaken toepassing geslaagd
     destroyed: Verwijderen toepassing geslaagd
@@ -351,7 +359,8 @@ nl:
     your_token: Jouw toegangscode
   auth:
     agreement_html: Wanneer je op registreren klikt ga je akkoord met het opvolgen van <a href="%{rules_path}">de regels van deze server</a> en <a href="%{terms_path}">onze gebruikersvoorwaarden</a>.
-    change_password: Beveiliging
+    change_password: Wachtwoord
+    confirm_email: E-mail bevestigen
     delete_account: Account verwijderen
     delete_account_html: Wanneer je jouw account graag wilt verwijderen, kan je dat <a href="%{path}">hier doen</a>. We vragen jou daar om een bevestiging.
     didnt_get_confirmation: Geen bevestigingsinstructies ontvangen?
@@ -361,9 +370,16 @@ nl:
     logout: Afmelden
     migrate_account: Naar een andere account verhuizen
     migrate_account_html: Wanneer je dit account naar een ander account wilt doorverwijzen, kun je <a href="%{path}">dit hier instellen</a>.
+    or: of
+    or_log_in_with: Of aanmelden met
+    providers:
+      cas: CAS
+      saml: SAML
     register: Registreren
+    register_elsewhere: Op een andere server registreren
     resend_confirmation: Verstuur de bevestigingsinstructies nogmaals
     reset_password: Wachtwoord opnieuw instellen
+    security: Beveiliging
     set_new_password: Nieuw wachtwoord instellen
   authorize_follow:
     error: Helaas, er is een fout opgetreden bij het opzoeken van de externe account
@@ -410,6 +426,13 @@ nl:
       title: Er is iets mis
     noscript_html: Schakel JavaScript in om de webapp van Mastodon te kunnen gebruiken. Als alternatief kan je een <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">Mastodon-app</a> zoeken voor jouw platform.
   exports:
+    archive_takeout:
+      date: Datum
+      download: Jouw archief downloaden
+      hint_html: Je kunt een archief opvragen van jouw <strong>toots en geüploade media</strong>. De geëxporteerde gegevens zijn in ActivityPub-formaat, dat door hiervoor geschikte software valt uit te lezen.
+      in_progress: Jouw archief wordt samengesteld...
+      request: Jouw archief opvragen
+      size: Omvang
     blocks: Jij blokkeert
     csv: CSV
     follows: Jij volgt
@@ -417,7 +440,7 @@ nl:
     storage: Mediaopslag
   followers:
     domain: Domein
-    explanation_html: Wanneer je de privacy van jouw toots wilt garanderen, moet je goed weten wie jouw volgers zijn. <strong>Toots die alleen aan jouw volgers zijn gericht, worden aan de Mastodon-servers van jouw volgers afgeleverd.</strong> Daarom wil je ze misschien controleren en desnoods volgers verwijderen die zich op een Mastodon-server bevinden die jij niet vertrouwd. Bijvoorbeeld omdat de beheerder(s) of de software van zo'n server jouw privacy niet respecteert.
+    explanation_html: Wanneer je de privacy van jouw toots wilt garanderen, moet je goed weten wie jouw volgers zijn. <strong>Toots die alleen aan jouw volgers zijn gericht, worden aan de Mastodonservers van jouw volgers afgeleverd.</strong> Daarom wil je ze misschien controleren en desnoods volgers verwijderen die zich op een Mastodonserver bevinden die jij niet vertrouwd. Bijvoorbeeld omdat de beheerder(s) of de software van zo'n server jouw privacy niet respecteert.
     followers_count: Aantal volgers
     lock_link: Maak jouw account besloten
     purge: Volgers verwijderen
@@ -458,7 +481,7 @@ nl:
       one: 1 keer
       other: "%{count} keer"
     max_uses_prompt: Onbeperkt
-    prompt: Genereer en deel speciale links om mensen toegang tot deze Mastodon-server te geven
+    prompt: Genereer en deel speciale links om mensen toegang tot deze Mastodonserver te geven
     table:
       expires_at: Verloopt op
       uses: Aantal keer te gebruiken
@@ -481,6 +504,7 @@ nl:
     title: Moderatie
   notification_mailer:
     digest:
+      action: Alle meldingen bekijken
       body: Hier is een korte samenvatting van de berichten die je sinds jouw laatste bezoek op %{since} hebt gemist
       mention: "%{name} vermeldde jou in:"
       new_followers_summary:
@@ -489,21 +513,29 @@ nl:
       subject:
         one: "1 nieuwe melding sinds jouw laatste bezoek \U0001F418"
         other: "%{count} nieuwe meldingen sinds jouw laatste bezoek \U0001F418"
+      title: Tijdens jouw afwezigheid…
     favourite:
       body: 'Jouw toot werd door %{name} als favoriet gemarkeerd:'
       subject: "%{name} markeerde jouw toot als favoriet"
+      title: Nieuwe favoriet
     follow:
       body: "%{name} volgt jou nu!"
       subject: "%{name} volgt jou nu"
+      title: Nieuwe volger
     follow_request:
+      action: Volgverzoeken beheren
       body: "%{name} wil jou graag volgen"
       subject: 'Volgen in afwachting: %{name}'
+      title: Nieuw volgverzoek
     mention:
+      action: Reageren
       body: 'Jij bent door %{name} vermeld in:'
       subject: Jij bent vermeld door %{name}
+      title: Nieuwe vermelding
     reblog:
       body: 'Jouw toot werd door %{name} geboost:'
       subject: "%{name} boostte jouw toot"
+      title: Nieuwe boost
   number:
     human:
       decimal_units:
@@ -516,7 +548,9 @@ nl:
           trillion: T
           unit: " "
   pagination:
+    newer: Nieuwer
     next: Volgende
+    older: Ouder
     prev: Vorige
     truncate: "&hellip;"
   preferences:
@@ -551,12 +585,14 @@ nl:
       blackberry: Blackberry
       chrome: Chrome
       edge: Microsoft Edge
+      electron: Electron
       firefox: Firefox
       generic: Onbekende webbrowser
       ie: Internet Explorer
       micro_messenger: MicroMessenger
       nokia: Nokia S40 Ovi Browser
       opera: Opera
+      otter: Otter
       phantom_js: PhantomJS
       qq: QQ Browser
       safari: Safari
@@ -564,7 +600,7 @@ nl:
       weibo: Weibo
     current_session: Huidige sessie
     description: "%{browser} op %{platform}"
-    explanation: Dit zijn de webbrowsers die momenteel met jouw Mastodon-account zijn ingelogd.
+    explanation: Dit zijn de webbrowsers die momenteel met jouw Mastodonaccount zijn ingelogd.
     ip: IP
     platforms:
       adobe_air: Adobe Air
@@ -708,7 +744,31 @@ nl:
     recovery_instructions_html: Wanneer je ooit de toegang verliest tot jouw telefoon, kan je met behulp van een van de herstelcodes hieronder opnieuw toegang krijgen tot jouw account. <strong>Zorg ervoor dat je de herstelcodes op een veilige plek bewaard</strong>. Je kunt ze bijvoorbeeld printen en ze samen met andere belangrijke documenten bewaren.
     setup: Instellen
     wrong_code: De ingevoerde code is ongeldig! Klopt de systeemtijd van de server en die van jouw apparaat?
+  user_mailer:
+    backup_ready:
+      explanation: Je hebt een volledige back-up van jouw Mastodon-account opgevraagd. Het staat nu klaar om te worden gedownload!
+      subject: Jouw archief staat klaar om te worden gedownload
+      title: Archief ophalen
+    welcome:
+      edit_profile_action: Profiel instellen
+      edit_profile_step: Je kunt jouw profiel aanpassen door een avatar (profielfoto) en omslagfoto te uploaden, jouw weergavenaam in te stellen en iets over jezelf te vertellen. Wanneer je nieuwe volgers eerst wilt goedkeuren, kun je jouw account besloten maken.
+      explanation: Hier zijn enkele tips om je op weg te helpen
+      final_action: Begin berichten te plaatsen
+      final_step: 'Begin berichten te plaatsen! Zelfs zonder volgers kunnen jouw openbare berichten door anderen gezien worden, bijvoorbeeld op de lokale tijdlijn en via hashtags. Je wilt jezelf misschien introduceren met de hashtag #introductions.'
+      full_handle: Jouw volledige Mastodonadres
+      full_handle_hint: Dit geef je aan jouw vrienden, zodat ze jouw berichten kunnen sturen of (vanaf een andere Mastodonserver) kunnen volgen.
+      review_preferences_action: Instellingen veranderen
+      review_preferences_step: Zorg dat je jouw instellingen naloopt, zoals welke e-mails je wilt ontvangen of voor wie jouw berichten standaard zichtbaar moeten zijn. Wanneer je geen last hebt van bewegende beelden, kun je het afspelen van geanimeerde GIF's inschakelen.
+      subject: Welkom op Mastodon
+      tip_bridge_html: Wanneer je hiervoor op Twitter zat, kun je jouw vrienden op Mastodon vinden door gebruik te maken van de <a href="%{bridge_url}">bridge-app</a>. Het werkt echter alleen wanneer zij ook deze bridge-app hebben gebruikt!
+      tip_federated_timeline: De globale tijdlijn toont berichten in het Mastodonnetwerk. Het bevat echter alleen berichten van mensen waar jouw buren mee zijn verbonden, dus het is niet compleet.
+      tip_following: Je volgt standaard de beheerder(s) van jouw Mastodonserver. Bekijk de lokale en de globale tijdlijnen om meer interessante mensen te vinden.
+      tip_local_timeline: De lokale tijdlijn toont berichten van mensen op %{instance}. Dit zijn jouw naaste buren!
+      tip_mobile_webapp: Wanneer jouw mobiele webbrowser Mastodon aan jouw startscherm wilt toevoegen, kun je pushmeldingen ontvangen. Het gedraagt zich op meerdere manieren als een native app!
+      tips: Tips
+      title: Welkom aan boord %{name}!
   users:
     invalid_email: E-mailadres is ongeldig
     invalid_otp_token: Ongeldige tweestaps-aanmeldcode
+    seamless_external_login: Je bent ingelogd via een externe dienst, daarom zijn wachtwoorden en e-mailinstellingen niet beschikbaar.
     signed_in_as: 'Ingelogd als:'
diff --git a/config/locales/no.yml b/config/locales/no.yml
index 3b212932e..3adf71bee 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -15,49 +15,84 @@
       <h3>En god plassering for regler</h3>
       <p>En utvidet beskrivelse er ikke satt opp ennå.</p>
     features:
+      humane_approach_body: Mastodon har tatt lærdom fra andre nettverk og har til mål å gjøre etiske designvalg for å bekjempe misbruk av sosiale medier.
       humane_approach_title: En mer menneskelig tilnærming
       not_a_product_body: Mastodon er ikke et kommerst nettverk. Ingen reklame, ingen datainnsamling, ingen innhegnede hager. Det finnes ingen sentral myndighet.
       not_a_product_title: Du er en person, ikke et produkt
+      real_conversation_body: Med 500 tegn til din disposisjon og støtte for granulært innhold og media-advarsler kan du uttrykke deg på den måten du selv vil.
+      real_conversation_title: Laget for ekte samtaler
+      within_reach_body: Takket være et utviklingsvennlig API-økosystem vil flere apper for iOS, Android og andre plattformer la deg holde kontakten med dine venner hvor som helst.
+      within_reach_title: Alltid innen rekkevidde
+    generic_description: "%{domain} er en tjener i nettverket"
+    hosted_on: Mastodon driftet på %{domain}
+    learn_more: Lær mer
     other_instances: Andre instanser
     source_code: Kildekode
     status_count_after: statuser
     status_count_before: Som skrev
     user_count_after: brukere
     user_count_before: Her bor
+    what_is_mastodon: Hva er Mastodon?
   accounts:
     follow: Følg
     followers: Følgere
     following: Følger
+    media: Media
+    moved_html: "%{name} har flyttet til %{new_profile_link}:"
     nothing_here: Det er ingenting her!
     people_followed_by: Folk som %{name} følger
     people_who_follow: Folk som følger %{name}
     posts: Poster
+    posts_with_replies: Tuter med svar
     remote_follow: Følg fra andre instanser
-    unfollow: Avfølg
+    reserved_username: Brukernavnet er reservert
+    roles:
+      admin: Admin
+      moderator: Moderere
+    unfollow: Slutte følge
   admin:
+    account_moderation_notes:
+      account: Moderator
+      create: Lag
+      created_at: Dato
+      created_msg: Moderasjonsnotat laget uten problem!
+      delete: Slett
+      destroyed_msg: Moderasjonsnotat slettet uten problem!
     accounts:
       are_you_sure: Er du sikker?
+      by_domain: Domene
       confirm: Bekreft
       confirmed: Bekreftet
+      demote: Degrader
+      disable: Deaktiver
       disable_two_factor_authentication: Skru av 2FA
+      disabled: Deaktivert
       display_name: Visningsnavn
       domain: Domene
       edit: Redigér
       email: E-post
+      enable: Aktiver
+      enabled: Aktivert
       feed_url: Feed-URL
       followers: Følgere
+      followers_url: Følgere URL
       follows: Følginger
+      inbox_url: Innboks URL
+      ip: IP-adresse
       location:
         all: Alle
         local: Lokalt
         remote: Fjernt
         title: Sted
+      login_status: Innloggingsstatus
       media_attachments: Mediavedlegg
+      memorialize: Gjør om til et minne
       moderation:
         all: Alle
         silenced: Målbundet
         suspended: Utvist
         title: Moderasjon
+      moderation_notes: Moderasjonsnotater
       most_recent_activity: Nyligste aktivitet
       most_recent_ip: Nyligste IP
       not_subscribed: Ikke abonnért
@@ -65,23 +100,90 @@
         alphabetic: Alfabetisk
         most_recent: Nyligst
         title: Rekkefølge
+      outbox_url: Utboks URL
       perform_full_suspension: Utfør full utvisning
       profile_url: Profil-URL
+      promote: Oppgradere
+      protocol: Protokoll
       public: Offentlig
       push_subscription_expires: PuSH-abonnent utløper
+      redownload: Oppdater avatar
+      reset: Tilbakestill
       reset_password: Nullstill passord
+      resubscribe: Abonner på nytt
+      role: Rettigheter
+      roles:
+        admin: Administrator
+        moderator: Moderator
+        staff: Personale
+        user: Bruker
       salmon_url: Salmon-URL
+      search: Søk
+      shared_inbox_url: Delt Innboks URL
       show:
         created_reports: Rapporter laget av denne kontoen
         report: rapport
         targeted_reports: Rapporter laget om denne kontoen
       silence: Målbind
       statuses: Statuser
+      subscribe: Abonnere
       title: Kontoer
       undo_silenced: Angre målbinding
       undo_suspension: Angre utvisning
+      unsubscribe: Avslutte abonnementet
       username: Brukernavn
       web: Web
+    action_logs:
+      actions:
+        confirm_user: "%{name} bekreftet e-postadresse for bruker %{target}"
+        create_custom_emoji: "%{name} lastet opp ny emoji %{target}"
+        create_domain_block: "%{name} blokkerte domenet %{target}"
+        create_email_domain_block: "%{name} svartelistet e-postdomenet %{target}"
+        demote_user: "%{name} degraderte bruker %{target}"
+        destroy_domain_block: "%{name} fjernet blokkeringen av domenet %{target}"
+        destroy_email_domain_block: "%{name} hvitelistet e-postdomenet %{target}"
+        destroy_status: "%{name} fjernet status av %{target}"
+        disable_2fa_user: "%{name} deaktiverte tofaktor-autentiseringskravet for bruker %{target}"
+        disable_custom_emoji: "%{name} deaktiverte emoji %{target}"
+        disable_user: "%{name} deaktiverte innlogging for bruker %{target}"
+        enable_custom_emoji: "%{name} aktiverte emoji %{target}"
+        enable_user: "%{name} aktiverte innlogging for bruker %{target}"
+        memorialize_account: "%{name} endret %{target}s konto til en minneside"
+        promote_user: "%{name} oppgraderte bruker %{target}"
+        reset_password_user: "%{name} nullstilte passordet til bruker %{target}"
+        resolve_report: "%{name} avviste rapporten %{target}"
+        silence_account: "%{name} forstummet %{target}s konto"
+        suspend_account: "%{name} suspendert %{target}s konto"
+        unsilence_account: "%{name} fjernet forstummingen av %{target}s konto"
+        unsuspend_account: "%{name} opphevde suspenderingen av %{target}s konto"
+        update_custom_emoji: "%{name} oppdaterte emoji %{target}"
+        update_status: "%{name} oppdaterte status for %{target}"
+      title: Revisionslogg
+    custom_emojis:
+      by_domain: Domene
+      copied_msg: Laget en lokal kopi av emoji uten problem
+      copy: Kopiere
+      copy_failed_msg: Kunne ikke lage en lokal kopi av den emojien
+      created_msg: Emoji laget uten problem!
+      delete: Slett
+      destroyed_msg: Emojo slettet uten problem!
+      disable: Deaktivere
+      disabled_msg: Deaktiverte emoji uten problem
+      emoji: Emoji
+      enable: Aktivere
+      enabled_msg: Aktiverte emojien uten problem
+      image_hint: PNG opp til 50KB
+      listed: Oppførte
+      new:
+        title: Legg til ny egen emoji
+      overwrite: Overskrive
+      shortcode: Kortkode
+      shortcode_hint: Minst 2 tegn, kun alfanumeriske tegn og understrek
+      title: Egenoppførte emojier
+      unlisted: Unoterte
+      update_failed_msg: Kunne ikke oppdatere emojien
+      updated_msg: Emoji oppdatert uten problemer!
+      upload: Last opp
     domain_blocks:
       add_new: Lag ny
       created_msg: Domeneblokkering blir nå behandlet
@@ -92,12 +194,14 @@
         hint: Domeneblokkeringen vil ikke hindre opprettelse av kontooppføringer i databasen, men vil retroaktivt og automatisk benytte spesifikke moderasjonsmetoder på de kontoene.
         severity:
           desc_html: "<strong>Målbind</strong> gjør kontoens poster usynlige for alle som ikke følger den. <strong>Utvis</strong> fjerner alt innhold, media og profildata fra kontoen. Bruk <strong>Ingen</strong> hvis du bare vil fjerne mediafiler."
+          noop: Ingen
           silence: Målbind
           suspend: Utvis
         title: Ny domeneblokkering
       reject_media: Avvis mediefiler
       reject_media_hint: Fjerner lokalt lagrede mediefiler og nekter å laste dem ned i fremtiden. Irrelevant for utvisninger
       severities:
+        noop: Ingen
         silence: Målbind
         suspend: Utvis
       severity: Alvorlighet
@@ -112,17 +216,41 @@
         undo: Angre
       title: Domeneblokkeringer
       undo: Angre
+    email_domain_blocks:
+      add_new: Lag ny
+      created_msg: E-postdomenet ble lagt til i blokkeringslisten uten problemer
+      delete: Fjern
+      destroyed_msg: E-postdomenet har blitt fjernet fra blokkeringslisten uten problemer
+      domain: Domene
+      new:
+        create: Legg til domene
+        title: Ny blokkeringsoppføring av e-postdomene
+      title: Blokkering av e-postdomene
     instances:
       account_count: Kjente kontoer
       domain_name: Domene
+      reset: Tilbakestill
+      search: Søk
       title: Kjente instanser
+    invites:
+      filter:
+        all: Alle
+        available: Tilgjengelig
+        expired: Utløpt
+        title: Filter
+      title: Invitasjoner
     reports:
+      action_taken_by: Handling utført av
+      are_you_sure: Er du sikker?
       comment:
         label: Kommentar
         none: Ingen
       delete: Slett
       id: ID
       mark_as_resolved: Merk som løst
+      nsfw:
+        'false': Vis mediavedlegg
+        'true': Skjul mediavedlegg
       report: 'Rapportér #%{id}'
       report_contents: Innhold
       reported_account: Rapportert konto
@@ -136,23 +264,66 @@
       unresolved: Uløst
       view: Vis
     settings:
+      activity_api_enabled:
+        desc_html: Antall lokale statusposter, aktive brukere og nye registreringer i ukentlige oppdelinger
+        title: Publiser samlet statistikk om brukeraktiviteter
+      bootstrap_timeline_accounts:
+        desc_html: Separer flere brukernavn med komma. Kun lokale og ulåste kontoer vil kunne brukes. Dersom tomt er standarden alle lokale administratorer.
+        title: Standard følgere for nye brukere
       contact_information:
         email: Skriv en offentlig e-postadresse
         username: Skriv brukernavn
+      peers_api_enabled:
+        desc_html: Domenenavn denne instansen har truffet på i fediverset
+        title: Publiser liste over oppdagede instanser
       registrations:
         closed_message:
           desc_html: Vises på forsiden når registreringer er lukket<br>Du kan bruke HTML-tagger
           title: Melding for lukket registrering
+        deletion:
+          desc_html: Tillat alle å slette sin konto
+          title: Åpne kontosletting
+        min_invite_role:
+          disabled: Ingen
+          title: Tillat invitasjoner fra
         open:
+          desc_html: Tillatt alle å lage seg en konto
           title: Åpen registrering
+      show_staff_badge:
+        desc_html: Vis personalemerke på brukersiden
+        title: Vis personalemerke
       site_description:
         desc_html: Vises som et avsnitt på forsiden og brukes som en meta-tagg. Du kan bruke HTML-tagger, spesielt <code>&lt;a&gt;</code> og <code>&lt;em&gt;</code>.
         title: Nettstedsbeskrivelse
       site_description_extended:
         desc_html: Vises på side for utvidet informasjon.<br>Du kan bruke HTML-tagger
         title: Utvidet nettstedsinformasjon
+      site_terms:
+        desc_html: Du kan skrive din egen personverns-strategi, bruksviklår og andre regler. Du kan bruke HTML tagger
+        title: Skreddersydde bruksvilkår
       site_title: Nettstedstittel
+      thumbnail:
+        desc_html: Brukes ved forhandsvisning via OpenGraph og API. 1200x630px anbefales
+        title: Miniatyrbilde for instans
+      timeline_preview:
+        desc_html: Vis offentlig tidslinje på landingssiden
+        title: Forhandsvis tidslinjen
       title: Nettstedsinnstillinger
+    statuses:
+      back_to_account: Tilbake til kontosiden
+      batch:
+        delete: Slett
+        nsfw_off: NSFW AV
+        nsfw_on: NSFW PÅ
+      execute: Utfør
+      failed_to_execute: Utføring mislyktes
+      media:
+        hide: Skjul media
+        show: Vis media
+        title: Media
+      no_media: Ingen media
+      title: Kontostatuser
+      with_media: Med media
     subscriptions:
       callback_url: Callback-URL
       confirmed: Bekreftet
@@ -161,24 +332,50 @@
       title: WebSub
       topic: Emne
     title: Administrasjon
+  admin_mailer:
+    new_report:
+      body: "%{reporter} har rapportert %{target}"
+      subject: Ny rapport for %{instance} (#%{id})
   application_mailer:
+    notification_preferences: Endre e-post innstillingene
+    salutation: "%{name},"
     settings: 'Endre foretrukne e-postinnstillinger: %{link}'
     view: 'Se:'
+    view_profile: Vis Profil
+    view_status: Vis status
   applications:
+    created: Søknaden ble vellykket oppretttet
+    destroyed: Søknaden ble vellykket slettet
     invalid_url: Den oppgitte URLen er ugyldig
+    regenerate_token: Regenerer tilgangsnøkkel
+    token_regenerated: Tilgangsnøkkel vellykket regenerert
+    warning: Vær veldig forsiktig med denne data. Aldri del den med noen!
+    your_token: Din tilgangsnøkkel
   auth:
-    change_password: Brukerdetaljer
-    didnt_get_confirmation: Fikk du ikke bekreftelsesmailen din?
+    agreement_html: Ved å registrere deg godtar du å følge <a href="%{rules_path}">instansens regler</a> og <a href="%{terms_path}">våre brukervilkår</a>.
+    delete_account: Slett konto
+    delete_account_html: Hvis du ønsker å slette din konto kan du <a href="%{path}">fortsette her</a>. Du vil bli spurt om bekreftelse.
+    didnt_get_confirmation: Mottok du ikke instruksjoner om bekreftelse?
     forgot_password: Har du glemt passordet ditt?
+    invalid_reset_password_token: Tilbakestillingsnøkkelen for passord er ugyldig eller utløpt. Vennligst be om en ny.
     login: Innlogging
     logout: Logg ut
+    migrate_account: Flytt til en annen konto
+    migrate_account_html: Hvis du ønsker å henvise denne kontoen til en annen, kan du <a href="%{path}">konfigurere det her</a>.
     register: Bli med
     resend_confirmation: Send bekreftelsesinstruksjoner på nytt
     reset_password: Nullstill passord
+    security: Sikkerhet
     set_new_password: Sett nytt passord
   authorize_follow:
     error: Uheldigvis skjedde det en feil da vi prøvde å få tak i en bruker fra en annen instans
     follow: Følg
+    follow_request: 'Du har sendt en følgeforespørsel til:'
+    following: 'Suksess! Nå følger du:'
+    post_follow:
+      close: Eller så kan du lukke dette vinduet.
+      return: Gå tilbake til brukerens profil
+      web: Gå til nettsiden
     title: Følg %{acct}
   datetime:
     distance_in_words:
@@ -194,6 +391,14 @@
       x_minutes: "%{count} min"
       x_months: "%{count} mnd"
       x_seconds: "%{count} sek"
+  deletes:
+    bad_password_msg: Godt forsøk, hacker! Feil passord
+    confirm_password: Skriv inn ditt passord for å verifisere din identitet
+    description_html: Dette vil <strong>permanent, irreversibelt</strong> fjerne innhold fra din konto og deaktivere den. Ditt brukernavn vil forbli reservert for å forhindre fremtidige etterligninger.
+    proceed: Slett konto
+    success_msg: Din konto ble slettet
+    warning_html: Kun sletting av innhold fra denne instansen er garantert. Innhold som har blitt delt i stor utstrekning vil sannsynligvis legge igjen spor. Tjenere som er frakoblet og tjenere som ikke abbonerer på dine oppdatereringer vil ikke oppdatere sine databaser.
+    warning_title: Tilgjengelighet av spredt innhold
   errors:
     '403': Du har ikke tillatelse til å vise denne siden.
     '404': Siden du leter etter finnes ikke.
@@ -202,6 +407,10 @@
       content: Sikkerhetsverifisering feilet. Blokkerer du informasjonskapsler?
       title: Sikkerhetsverifisering feilet
     '429': Overfyllt
+    '500':
+      content: Beklager men noe gikk galt ved vår ende.
+      title: Denne siden er ikke korrekt
+    noscript_html: For å bruke Mastodon webapplikasjon må du aktivere JavaScript. Alternativt kan du forsøke en av de mange <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">integrerte appene</a> for Mastodon til din plattform.
   exports:
     blocks: Du blokkerer
     csv: CSV
@@ -235,37 +444,77 @@
       following: Følgeliste
       muting: Dempeliste
     upload: Opplastning
+  in_memoriam_html: Til minne.
+  invites:
+    delete: Deaktiver
+    expired: Utløpt
+    expires_in:
+      '1800': 30 minutter
+      '21600': 6 timer
+      '3600': 1 time
+      '43200': 12 timer
+      '86400': 1 dag
+    expires_in_prompt: Aldri
+    generate: Generer
+    max_uses:
+      one: 1 uke
+      other: "%{count} bruk"
+    max_uses_prompt: Ubegrenset
+    prompt: Generer og del lenker med andre for å gi tilgang til denne instansen
+    table:
+      expires_at: Utløper
+      uses: Bruk
+    title: Inviter personer
   landing_strip_html: "<strong>%{name}</strong> er en bruker på %{link_to_root_path}. Du kan følge dem eller kommunisere med dem hvis du har en konto hvor som helst i fediverset."
   landing_strip_signup_html: Hvis du ikke har en konto så kan du <a href="%{sign_up_path}">registrere deg her</a>.
+  lists:
+    errors:
+      limit: Du har nådd det maksimale antall lister
   media_attachments:
     validations:
       images_and_video: Kan ikke legge ved video på en status som allerede inneholder bilder
       too_many: Kan ikke legge ved mer enn 4 filer
+  migrations:
+    acct: brukernavn@domene til den nye kontoen
+    currently_redirecting: 'Din profil er omdirigert til:'
+    proceed: Lagre
+    updated_msg: Dine innstillinger for kontomigrering er oppdatert!
+  moderation:
+    title: Moderasjon
   notification_mailer:
     digest:
-      body: 'Her er en kort oppsummering av hva du har gått glipp av på %{instance} siden du logget deg inn sist den %{since}:'
+      action: Vis alle varslinger
+      body: Her er en kort oppsummering av hva du har gått glipp av siden du sist logget inn den %{since}
       mention: "%{name} nevnte deg i:"
       new_followers_summary:
-        one: Du har fått en ny følger. Jippi!
-        other: Du har fått %{count} nye følgere! Imponerende!
+        one: I tillegg har du fått en ny følger mens du var borte. Hurra!
+        other: I tillegg har du har fått %{count} nye følgere mens du var borte! Imponerende!
       subject:
         one: "1 ny hendelse siden ditt siste besøk \U0001F418"
         other: "%{count} nye hendelser siden ditt siste besøk \U0001F418"
+      title: I ditt fravær…
     favourite:
       body: 'Statusen din ble likt av %{name}:'
       subject: "%{name} likte statusen din"
+      title: Ny favoritt
     follow:
       body: "%{name} følger deg!"
       subject: "%{name} følger deg"
+      title: Ny følger
     follow_request:
+      action: Behandle følgerforespørsler
       body: "%{name} har bedt om lov til å følge deg"
       subject: 'Ventende følginger: %{name}'
+      title: Ny følgerforespørsel
     mention:
+      action: Svar
       body: 'Du ble nevnt av %{name} i:'
       subject: Du ble nevnt av %{name}
+      title: Ny nevning
     reblog:
       body: 'Din status ble fremhevd av %{name}:'
       subject: "%{name} fremhevde din status"
+      title: Ny fremheving
   number:
     human:
       decimal_units:
@@ -276,30 +525,101 @@
           quadrillion: Q
           thousand: K
           trillion: T
-          unit: ''
+          unit: " "
   pagination:
     next: Neste
     prev: Forrige
     truncate: "&hellip;"
+  preferences:
+    languages: Språk
+    other: Annet
+    publishing: Publisering
+    web: Web
+  push_notifications:
+    favourite:
+      title: "%{name} favoriserte din status"
+    follow:
+      title: "%{name} følger deg nå"
+    group:
+      title: "%{count} varslinger"
+    mention:
+      action_boost: Fremhev
+      action_expand: Vis mer
+      action_favourite: Favoritter
+      title: "%{name} nevnte deg"
+    reblog:
+      title: "%{name} fremhevde din status"
   remote_follow:
     acct: Tast inn brukernavn@domene som du vil følge fra
     missing_resource: Kunne ikke finne URLen for din konto
     proceed: Fortsett med følging
     prompt: 'Du vil følge:'
+  sessions:
+    activity: Siste aktivitet
+    browser: Nettleser
+    browsers:
+      alipay: Alipay
+      blackberry: Blackberry
+      chrome: Chrome
+      edge: Microsoft Edge
+      electron: Electron
+      firefox: Firefox
+      generic: Ukjent nettleser
+      ie: Internet Explorer
+      micro_messenger: MicroMessenger
+      nokia: Nokia S40 Ovi Browser
+      opera: Opera
+      otter: Otter
+      phantom_js: PhantomJS
+      qq: QQ Browser
+      safari: Safari
+      uc_browser: UCBrowser
+      weibo: Weibo
+    current_session: Nåværende økt
+    description: "%{browser} på %{platform}"
+    explanation: Dette er nettlesere innlogget på din Mastodon-konto akkurat nå.
+    ip: IP-adresse
+    platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: Blackberry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: Linux
+      mac: Mac
+      other: ukjent plattform
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
+    revoke: Tilbakekall
+    revoke_success: Økt tilbakekalt
+    title: Økter
   settings:
     authorized_apps: Autoriserte applikasjoner
     back: Tilbake til Mastodon
+    delete: Kontosletting
+    development: Utvikling
     edit_profile: Endre profil
     export: Dataeksport
     followers: Godkjente følgere
     import: Importér
+    migrate: Kontomigrering
+    notifications: Varslinger
     preferences: Preferanser
     settings: Innstillinger
     two_factor_authentication: Tofaktorautentisering
+    your_apps: Dine applikasjoner
   statuses:
     open_in_web: Åpne i nettleser
     over_character_limit: grense på %{max} tegn overskredet
+    pin_errors:
+      limit: Du har allerede festet det maksimale antall tuter
+      ownership: Kun egne tuter kan festes
+      private: Kun offentlige tuter kan festes
+      reblog: En fremheving kan ikke festes
     show_more: Vis mer
+    title: '%{name}: "%{quote}"'
     visibilities:
       private: Privat
       private_long: Synlig kun for følgere
@@ -309,6 +629,7 @@
       unlisted_long: Synlig for alle, men ikke på offentlige tidslinjer
   stream_entries:
     click_to_show: Klikk for å vise
+    pinned: Festet tut
     reblogged: fremhevde
     sensitive_content: Følsomt innhold
   terms:
@@ -381,6 +702,8 @@
 
       <p>Dokumentet er en adoptert og endret versjon fra <a href="https://github.com/discourse/discourse">Discourse privacy policy</a>.</p>
     title: "%{instance} Personvern og villkår for bruk av nettstedet"
+  themes:
+    default: Mastodon
   time:
     formats:
       default: "%-d. %b %Y, %H:%M"
@@ -389,15 +712,37 @@
     description_html: Hvis du skrur på <strong>tofaktorautentisering</strong> må du ha din telefon for å logge inn. Denne vil generere koder som du må taste inn.
     disable: Skru av
     enable: Skru på
+    enabled: To-faktor autentisering er aktivert
     enabled_success: Aktivering av tofaktorautentisering vellykket
     generate_recovery_codes: Generér gjenopprettingskoder
     instructions_html: "<strong>Scan denne QR-koden med Google Authenticator eller en lignende app på telefonen din</strong>. Fra nå av vil denne applikasjonen generere koder for deg som skal brukes under innlogging."
     lost_recovery_codes: Gjenopprettingskoder lar deg gjenoppnå tilgang til din konto hvis du mister din telefon. Hvis du har mistet gjenopprettingskodene, kan du regenerere dem her. Dine gamle gjenopprettingskoder vil bli ugyldige.
     manual_instructions: 'Hvis du ikke får scannet QR-koden må du skrive inn følgende kode manuelt:'
-    recovery_codes_regenerated: Generering av gjenopprettingskoder vellykket
+    recovery_codes: Reservekoder
+    recovery_codes_regenerated: Generering av reservekoder fullført
     recovery_instructions_html: Hvis du skulle miste tilgang til telefonen din, kan du bruke en av gjenopprettingskodene nedenfor til å gjenopprette tilgang til din konto. <strong>Oppbevar gjenopprettingskodene sikkert</strong>, for eksempel ved å skrive dem ut og gjemme dem på et lurt sted bare du vet om.
     setup: Sett opp
     wrong_code: Den angitte koden var ugyldig! Stemmer instansens tid overalt med enhetens tid?
+  user_mailer:
+    welcome:
+      edit_profile_action: Sett opp profil
+      edit_profile_step: Du kan tilpasse din profil ved å laste opp en avatar, overskrift, endre ditt visningsnavn med mer. Hvis du vil godkjenne hvilke personer som får lov til å følge deg kan du låse kontoen.
+      explanation: Her er noen tips for å komme i gang
+      final_action: Start postingen
+      final_step: 'Start å poste! Selv uten følgere kan dine offentlige meldinger bli sett av andre, for eksempel på den lokale tidslinjen og i emneknagger. Du kan introdusere deg selv ved å bruke emneknaggen #introductions.'
+      full_handle: Ditt fullstendige brukernavn
+      full_handle_hint: Dette er hva du forteller venner slik at de kan sende melding eller følge deg fra en annen instanse.
+      review_preferences_action: Endre innstillinger
+      review_preferences_step: Husk å justere dine innstillinger, som hvilke e-poster du ønsker å motta, eller hvor private du ønsker at dine poster skal være som standard. Hvis du ikke har bevegelsessyke kan du skru på automatisk avspilling av GIF-animasjoner.
+      subject: Velkommen til Mastodon
+      tip_bridge_html: Hvis du kommer fra Twitter kan du finne dine venner på Mastodon ved å bruke en <a href="%{bridge_url}">bro app</a>. Men det fungerer kun dersom de også bruker bro-appen!
+      tip_federated_timeline: Den forente tidslinjen blir konstant matet med meldinger fra Mastodon-nettverket. Men den inkluderer bare personer dine naboer abbonerer på, så den er ikke komplett.
+      tip_following: Du følger din tjeners administrator(er) som standard. For å finne mer interessante personer, sjekk den lokale og forente tidslinjen.
+      tip_local_timeline: Den lokale tidslinjen blir kontant matet med meldinger fra personer på %{instance}. Dette er dine nærmeste naboer!
+      tip_mobile_webapp: Hvis din mobile nettleser tilbyr deg å legge Mastadon til din hjemmeskjerm kan du motta push-varslinger. Det er nesten som en integrert app på mange måter!
+      tips: Tips
+      title: Velkommen ombord, %{name}!
   users:
     invalid_email: E-postaddressen er ugyldig
     invalid_otp_token: Ugyldig to-faktorkode
+    signed_in_as: 'Innlogget som:'
diff --git a/config/locales/oc.yml b/config/locales/oc.yml
index beb5d1f87..160bbc3ed 100644
--- a/config/locales/oc.yml
+++ b/config/locales/oc.yml
@@ -23,7 +23,6 @@ oc:
       real_conversation_title: Fach per de conversacions vertadièras
       within_reach_body: Multiplas aplicacion per iOS, Android, e autras plataformas mercés a un entorn API de bon utilizar, vos permet de gardar lo contacte pertot.
       within_reach_title: Totjorn al costat
-    find_another_instance: Trobar mai instàncias
     generic_description: "%{domain} es un dels servidors del malhum"
     hosted_on: Mastodon albergat sus %{domain}
     learn_more: Ne saber mai
@@ -159,7 +158,7 @@ oc:
         unsuspend_account: "%{name} restabliguèt lo compte a %{target}"
         update_custom_emoji: "%{name} metèt a jorn l’emoji %{target}"
         update_status: "%{name} metèt a jorn l’estatut a %{target}"
-      title: Audit log
+      title: Audit dels jornals
     custom_emojis:
       by_domain: Domeni
       copied_msg: Còpia locala de l’emoji ben creada
@@ -167,7 +166,7 @@ oc:
       copy_failed_msg: Fracàs de la còpia locala de l’emoji
       created_msg: Emoji ben creat !
       delete: Suprimir
-      destroyed_msg: Emojo ben suprimit !
+      destroyed_msg: Emoji ben suprimit !
       disable: Desactivar
       disabled_msg: Aqueste emoji es ben desactivat
       emoji: Emoji
@@ -282,13 +281,13 @@ oc:
           desc_html: Afichat sus las pagina d’acuèlh quand las inscripcions son tampadas.<br>Podètz utilizar de balisas HTML
           title: Messatge de barradura de las inscripcions
         deletion:
-          desc_html: Autorizar lo monde a suprimir lor compte
+          desc_html: Autorizar lo mond a suprimir lor compte
           title: Possibilitat de suprimir lo compte
         min_invite_role:
           disabled: Degun
           title: Autorizat amb invitacions
         open:
-          desc_html: Autorizar lo monde a se marcar
+          desc_html: Autorizar lo mond a se marcar
           title: Inscripcions
       show_staff_badge:
         desc_html: Mostrar lo badge Personal sus la pagina de perfil
@@ -338,9 +337,12 @@ oc:
       body: "%{reporter} a senhalat %{target}"
       subject: Novèl senhalament per %{instance} (#%{id})
   application_mailer:
+    notification_preferences: Cambiar las preferéncias de corrièl
     salutation: "%{name},"
     settings: 'Cambiar las preferéncias de corrièl : %{link}'
     view: 'Veire :'
+    view_profile: Veire lo perfil
+    view_status: Veire los estatuts
   applications:
     created: Aplicacion ben creada
     destroyed: Aplication ben suprimida
@@ -351,7 +353,6 @@ oc:
     your_token: Vòstre geton d’accès
   auth:
     agreement_html: En vos marcar acceptatz <a href="%{rules_path}">las règlas de l’instància</a> e <a href="%{terms_path}">politica de confidencialitat</a>.
-    change_password: Seguretat
     delete_account: Suprimir lo compte
     delete_account_html: Se volètz suprimir vòstre compte, podètz <a href="%{path}">o far aquí</a>. Vos demandarem que confirmetz.
     didnt_get_confirmation: Avètz pas recebut las instruccions de confirmacion ?
@@ -361,9 +362,13 @@ oc:
     logout: Se desconnectar
     migrate_account: Mudar endacòm mai
     migrate_account_html: Se volètz mandar los visitors d’aqueste compte a un autre, podètz<a href="%{path}"> o configurar aquí</a>.
+    providers:
+      cas: CAS
+      saml: SAML
     register: Se marcar
     resend_confirmation: Tornar mandar las instruccions de confirmacion
     reset_password: Reïnicializar lo senhal
+    security: Seguretat
     set_new_password: Picar un nòu senhal
   authorize_follow:
     error: O planhèm, i a agut una error al moment de cercar lo compte
@@ -385,7 +390,7 @@ oc:
     - dv
     - ds
     abbr_month_names:
-    - 
+    - None
     - gen
     - feb
     - mar
@@ -411,7 +416,7 @@ oc:
       long: Lo %e %B de %Y
       short: "%e %b. de %Y"
     month_names:
-    - 
+    - None
     - de genièr
     - de febrièr
     - de març
@@ -535,12 +540,12 @@ oc:
     max_uses:
       one: 1 persona
       other: "%{count} personas"
-    max_uses_prompt: Cap limit
+    max_uses_prompt: Cap de limit
     prompt: Generatz e partejatz los ligams per donar accès a aquesta instància
     table:
       expires_at: Expirats
       uses: Usatges
-    title: Convidar de monde
+    title: Convidar de mond
   landing_strip_html: "<strong>%{name}</strong> utiliza %{link_to_root_path}. Podètz lo/la sègre o interagir amb el o ela s’avètz un compte ont que siasque sul fediverse."
   landing_strip_signup_html: S’es pas lo cas, podètz <a href="%{sign_up_path}">vos marcar aquí</a>.
   lists:
@@ -556,32 +561,41 @@ oc:
     proceed: Enregistrar
     updated_msg: Vòstre paramètre de migracion es ben estat mes a jorn !
   moderation:
-    title: Moderation
+    title: Moderacion
   notification_mailer:
     digest:
-      body: 'Trobatz aquí un resumit de çò qu’avètz mancat dempuèi vòstra darrièra visita lo %{since}:'
+      action: Veire totas las notificacions
+      body: 'Trobatz aquí un resumit dels messatges qu’avètz mancats dempuèi vòstra darrièra visita lo %{since} :'
       mention: "%{name} vos a mencionat dins :"
       new_followers_summary:
-        one: Avètz un nòu seguidor ! Ouà !
-        other: Avètz %{count} nòus seguidors ! Qué crane !
+        one: Avètz un nòu seguidor dempuèi vòstra darrièra visita ! Ouà !
+        other: Avètz %{count} nòus seguidors dempuèi vòstra darrièra visita ! Qué crane !
       subject:
         one: "Una nòva notificacion dempuèi vòstra darrièra visita \U0001F418"
         other: "%{count} nòvas notificacions dempuèi vòstra darrièra visita \U0001F418"
+      title: Pendent vòstra abséncia…
     favourite:
       body: "%{name} a mes vòstre estatut en favorit :"
       subject: "%{name} a mes vòstre estatut en favorit"
+      title: Novèl apondut als favorits
     follow:
       body: "%{name} vos sèc ara !"
       subject: "%{name} vos sèc ara"
+      title: Nòu seguidor
     follow_request:
+      action: Gerir las demandas d’abonament
       body: "%{name} a demandat a vos sègre"
       subject: 'Demanda d’abonament : %{name}'
+      title: Novèla demanda d’abonament
     mention:
+      action: Respondre
       body: "%{name} vos a mencionat dins :"
       subject: "%{name} vos a mencionat"
+      title: Novèla mencion
     reblog:
       body: "%{name} a tornat partejar vòstre estatut :"
       subject: "%{name} a tornat partejar vòstre estatut"
+      title: Novèl partatge
   number:
     human:
       decimal_units:
@@ -629,12 +643,14 @@ oc:
       blackberry: Blackberry
       chrome: Chrome
       edge: Microsoft Edge
+      electron: Electron
       firefox: Firefox
       generic: Navigator desconegut
       ie: Internet Explorer
       micro_messenger: MicroMessenger
       nokia: Nokia S40 Ovi Browser
       opera: Opera
+      otter: Otter
       phantom_js: PhantomJS
       qq: QQ Browser
       safari: Safari
@@ -786,6 +802,25 @@ oc:
     recovery_instructions_html: Se vos arriba de perdre vòstre mobil, podètz utilizar un dels còdis de recuperacion cai-jos per poder tornar accedir a vòstre compte. Gardatz los còdis en seguretat, per exemple, imprimissètz los e gardatz los amb vòstres documents importants.
     setup: Paramètres
     wrong_code: Lo còdi picat es invalid ! L’ora es la bona sul servidor e lo mobil ?
+  user_mailer:
+    welcome:
+      edit_profile_action: Configuracion del perfil
+      edit_profile_step: Podètz personalizar lo perfil en mandar un avatard, cambiar l’escais-nom e mai. Se volètz repassar las demandas d’abonaments abans que los nòus seguidors pòscan veire vòstre perfil, podètz clavar vòstre compte.
+      explanation: Vaquí qualques astúcias per vos preparar
+      final_action: Començar de publicar
+      final_step: 'Començatz de publicar ! Quitament s’avètz pas de seguidors los autres pòdon veire vòstres messatges publics, per exemple pel flux d’actualitat local e per las etiquetas. Benlèu que volètz vos presentar amb l’etiquetas #introductions.'
+      full_handle: Vòstre escais-nom complèt
+      full_handle_hint: Es aquò que vos cal donar a vòstres amics per que pòscan vos escriure o sègre a partir d’una autra instància.
+      review_preferences_action: Cambiar las preferéncias
+      review_preferences_step: Pensatz de configurar vòstras preferéncias, tal coma los corrièls que volètz recebrer o lo nivèl de confidencialitat de vòstres tuts per defaut. O se l’animacion vos dòna pas enveja de rendre, podètz activar la lectura automatica dels GIF.
+      subject: Benvengut a Mastodon
+      tip_bridge_html: Se venètz de Twitter, podètz trobar vòstres amics sus Mastodon en utilizant l‘<a href="%{bridge_url}">aplicacion de Pont</a>. Aquò fonciona pas que s’utilizan lo Pont tanben !
+      tip_federated_timeline: Lo flux d’actualitat federat es una vista generala del malhum Mastodon. Mas aquò inclutz solament lo mond que vòstres vesins sègon, doncas es pas complèt.
+      tip_following: Seguètz l’administrator del servidor per defaut. Per trobar de mond mai interessant, agachatz lo flux d’actualitat local e lo global.
+      tip_local_timeline: Lo flux d’actualitat local es una vista del mond de %{instance}. Son vòstres vesins dirèctes !
+      tip_mobile_webapp: Se vòstre navigator mobil nos permet d’apondre Mastodon a l’ecran d‘acuèlh, podètz recebre de notificacions. Aquò se compòrta coma una aplicacion nativa !
+      tips: Astúcias
+      title: Vos desirem la benvenguda a bòrd %{name} !
   users:
     invalid_email: L’adreça de corrièl es invalida
     invalid_otp_token: Còdi d’autentificacion en dos temps invalid
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index 949099a71..a09db429b 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -23,7 +23,6 @@ pl:
       real_conversation_title: Zaprojektowany do prawdziwych rozmów
       within_reach_body: Wiele aplikacji dla Androida, iOS i innych platform dzięki przyjaznemu programistom API sprawia, że możesz utrzymywać kontakt ze znajomymi praktycznie wszędzie.
       within_reach_title: Zawsze w Twoim zasięgu
-    find_another_instance: Znajdź inną instancję
     generic_description: "%{domain} jest jednym z serwerów sieci"
     hosted_on: Mastodon uruchomiony na %{domain}
     learn_more: Dowiedz się więcej
@@ -275,6 +274,9 @@ pl:
       contact_information:
         email: Służbowy adres e-mail
         username: Nazwa użytkownika do kontaktu
+      hero:
+        desc_html: Wyświetlany na stronie głównej. Zalecany jest rozmiar przynajmniej 600x100 pikseli. Jeżeli nie ustawiony, zostanie użyta miniatura instancji.
+        title: Obraz bohatera
       peers_api_enabled:
         desc_html: Nazwy domen, z którymi ta instancja wchodziła w interakcje
         title: Publikuj listę znanych instancji
@@ -291,6 +293,9 @@ pl:
         open:
           desc_html: Pozwól każdemu na założenie konta
           title: Otwarta rejestracja
+      show_known_fediverse_at_about_page:
+        desc_html: Jeśli włączone, podgląd instancji będzie wyświetlał wpisy z całego Fediwersum. W innym przypadku, będą wyświetlane tylko lokalne wpisy.
+        title: Pokazuj wszystkie znane wpisy na podglądzie instancji
       show_staff_badge:
         desc_html: Pokazuj odznakę uprawnień na stronie profilu użytkownika
         title: Pokazuj odznakę administracji
@@ -343,6 +348,7 @@ pl:
     salutation: "%{name},"
     settings: 'Zmień ustawienia powiadamiania: %{link}'
     view: 'Zobacz:'
+    view_profile: Wyświetl profil
     view_status: Wyświetl wpis
   applications:
     created: Pomyślnie utworzono aplikację
@@ -354,7 +360,8 @@ pl:
     your_token: Twój token dostępu
   auth:
     agreement_html: Rejestrując się, oświadczasz, że zapoznałeś się z <a href="%{rules_path}">informacjami o instancji</a> i <a href="%{terms_path}">zasadami korzystania z usługi</a>.
-    change_password: Bezpieczeństwo
+    change_password: Hasło
+    confirm_email: Potwierdź adres e-mail
     delete_account: Usunięcie konta
     delete_account_html: Jeżeli chcesz usunąć konto, <a href="%{path}">przejdź tutaj</a>. Otrzymasz prośbę o potwierdzenie.
     didnt_get_confirmation: Nie otrzymałeś instrukcji weryfikacji?
@@ -364,9 +371,16 @@ pl:
     logout: Wyloguj się
     migrate_account: Przenieś konto
     migrate_account_html: Jeżeli chcesz skonfigurować przekierowanie z obecnego konta na inne, możesz <a href="%{path}">skonfigurować to tutaj</a>.
+    or: lub
+    or_log_in_with: Lub zaloguj się z użyciem
+    providers:
+      cas: CAS
+      saml: SAML
     register: Rejestracja
+    register_elsewhere: Zarejestruj się na innym serwerze
     resend_confirmation: Ponownie prześlij instrukcje weryfikacji
     reset_password: Zresetuj hasło
+    security: Bezpieczeństwo
     set_new_password: Ustaw nowe hasło
   authorize_follow:
     error: Niestety, podczas sprawdzania zdalnego konta wystąpił błąd
@@ -409,10 +423,17 @@ pl:
       title: Sprawdzanie bezpieczeństwa nie powiodło się
     '429': Uduszono
     '500':
-      content: Przepraszamy, coś poszło nie tak…
+      content: Przepraszamy, coś poszło nie tak, po naszej stronie.
       title: Ta strona jest nieprawidłowa
     noscript_html: Aby korzystać z aplikacji Mastodon, włącz JavaScript. Możesz też skorzystać z jednej z <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">natywnych aplikacji</a> obsługującej Twoje urządzenie.
   exports:
+    archive_takeout:
+      date: Data
+      download: Pobierz swoje archiwum
+      hint_html: Możesz uzyskać archiwum swoich <strong>wpisów i wysłanej zawartości multimedialnej</strong>. Wyeksportowane dane będą dostępne w formacie ActivityPub, obsługiwanym przez odpowiednie programy.
+      in_progress: Tworzenie archiwum…
+      request: Uzyskaj archiwum
+      size: Rozmiar
     blocks: Zablokowani
     csv: CSV
     follows: Śledzeni
@@ -543,7 +564,9 @@ pl:
           trillion: T
           unit: ''
   pagination:
+    newer: Nowsze
     next: Następna
+    older: Starsze
     prev: Poprzednia
     truncate: "&hellip;"
   preferences:
@@ -740,6 +763,10 @@ pl:
     setup: Skonfiguruj
     wrong_code: Wprowadzony kod jest niepoprawny! Czy czas serwera i urządzenia jest poprawny?
   user_mailer:
+    backup_ready:
+      explanation: Zażądałeś pełnej kopii zapasowej konta na Mastodonie. Jest ono dostępne do pobrania
+      subject: Twoje archiwum jest gotowe do pobrania
+      title: Odbiór archiwum
     welcome:
       edit_profile_action: Skonfiguruj profil
       edit_profile_step: Możesz dostować profil wysyłając awatar, obraz nagłówka, zmieniając wyświetlaną nazwę i wiele więcej. Jeżeli chcesz, możesz zablokować konto, aby kontrolować, kto może Cię śledzić.
@@ -761,4 +788,5 @@ pl:
   users:
     invalid_email: Adres e-mail jest niepoprawny
     invalid_otp_token: Kod uwierzytelniający jest niepoprawny
+    seamless_external_login: Zalogowano z użyciem zewnętrznej usługi, więc ustawienia hasła i adresu e-mail nie są dostępne.
     signed_in_as: 'Zalogowany jako:'
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index c2830b754..a58eb8702 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -23,7 +23,6 @@ pt-BR:
       real_conversation_title: Feito para conversas reais
       within_reach_body: Vários apps para iOS, Android e outras plataformas graças a um ecossistema de API amigável para desenvolvedores permitem que você possa se manter atualizado sobre seus amigos de qualquer lugar.
       within_reach_title: Sempre ao seu alcance
-    find_another_instance: Encontre outra instância
     generic_description: "%{domain} é um servidor na rede"
     hosted_on: Mastodon hospedado em %{domain}
     learn_more: Saiba mais
@@ -274,6 +273,9 @@ pt-BR:
       contact_information:
         email: E-mail
         username: Contate usuário
+      hero:
+        desc_html: Aparece na página inicial. Ao menos 600x100px é recomendado. Se não estiver definido, o thumbnail da instância é usado no lugar
+        title: Imagem do herói
       peers_api_enabled:
         desc_html: Nomes de domínio que essa instância encontrou no fediverso
         title: Publicar lista de instâncias descobertas
@@ -290,6 +292,9 @@ pt-BR:
         open:
           desc_html: Permitir que qualquer um crie uma conta
           title: Cadastro aberto
+      show_known_fediverse_at_about_page:
+        desc_html: Quando ligado, vai mostrar toots de todo o fediverso conhecido na prévia da timeline. Senão, mostra somente toots locais.
+        title: Mostrar fediverso conhecido na prévia da timeline
       show_staff_badge:
         desc_html: Mostrar uma insígnia de Equipe na página de usuário
         title: Mostrar insígnia de equipe
@@ -338,9 +343,12 @@ pt-BR:
       body: "%{reporter} denunciou %{target}"
       subject: Nova denúncia sobre %{instance} (#%{id})
   application_mailer:
+    notification_preferences: Mudar preferências de e-mail
     salutation: "%{name},"
     settings: 'Mudar e-mail de preferência: %{link}'
     view: 'Visualizar:'
+    view_profile: Ver perfil
+    view_status: Ver status
   applications:
     created: Aplicação criada com sucesso
     destroyed: Aplicação excluída com sucesso
@@ -351,7 +359,8 @@ pt-BR:
     your_token: Seu token de acesso
   auth:
     agreement_html: Ao se cadastrar você concorda em seguir <a href="%{rules_path}">as regras da instância</a> e <a href="%{terms_path}">os nossos termos de serviço</a>.
-    change_password: Segurança
+    change_password: Senha
+    confirm_email: Confirmar e-mail
     delete_account: Excluir conta
     delete_account_html: Se você deseja excluir a sua conta, você pode <a href="%{path}">prosseguir para cá</a>. Uma confirmação será requisitada.
     didnt_get_confirmation: Não recebeu instruções de confirmação?
@@ -361,9 +370,16 @@ pt-BR:
     logout: Sair
     migrate_account: Mudar para uma conta diferente
     migrate_account_html: Se você quer redirecionar essa conta para uma outra você pode <a href="%{path}">configurar isso aqui</a>.
+    or: ou
+    or_log_in_with: Ou faça login com
+    providers:
+      cas: CAS
+      saml: SAML
     register: Cadastrar-se
+    register_elsewhere: Cadastrar-se em um outro servidor
     resend_confirmation: Reenviar instruções de confirmação
     reset_password: Redefinir senha
+    security: Segurança
     set_new_password: Definir uma nova senha
   authorize_follow:
     error: Infelizmente, ocorreu um erro ao buscar a conta remota
@@ -410,6 +426,13 @@ pt-BR:
       title: Esta página não está certa
     noscript_html: Para usar o aplicativo web do Mastodon, por favor ative o JavaScript. Ou, se quiser, experimente um dos <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">apps nativos</a> para o Mastodon em sua plataforma.
   exports:
+    archive_takeout:
+      date: Data
+      download: Baixe o seu arquivo
+      hint_html: Você pode pedir um arquivo dos seus <strong>toots e mídia enviada</strong>. Os dados exportados estarão no formato ActivityPub, que podem ser lidos por qualquer software compatível.
+      in_progress: Preparando seu arquivo...
+      request: Solicitar o seu arquivo
+      size: Tamanho
     blocks: Você bloqueou
     csv: CSV
     follows: Você segue
@@ -481,7 +504,8 @@ pt-BR:
     title: Moderação
   notification_mailer:
     digest:
-      body: 'Aqui está um resumo do que você perdeu no %{instance} desde o seu último acesso em %{since}:'
+      action: Ver todas as notificações
+      body: Aqui está um breve resumo das mensagens que você perdeu desde o seu último acesso em %{since}
       mention: "%{name} te mencionou em:"
       new_followers_summary:
         one: Você tem um novo seguidor! Yay!
@@ -489,21 +513,29 @@ pt-BR:
       subject:
         one: "Uma nova notificação desde o seu último acesso \U0001F418"
         other: "%{count} novas notificações desde o seu último acesso \U0001F418"
+      title: Enquanto você estava ausente…
     favourite:
       body: 'Sua postagem foi favoritada por %{name}:'
       subject: "%{name} favoritou a sua postagem"
+      title: Novo favorito
     follow:
       body: "%{name} está te seguindo!"
       subject: "%{name} está te seguindo"
+      title: Novo seguidor
     follow_request:
+      action: Gerenciar solicitações para seguir
       body: "%{name} requisitou autorização para te seguir"
       subject: 'Seguidor pendente: %{name}'
+      title: Nova solicitação de seguidor
     mention:
+      action: Responder
       body: 'Você foi mencionado por %{name} em:'
       subject: Você foi mencionado por %{name}
+      title: Nova menção
     reblog:
       body: 'Sua postagem foi compartilhada por %{name}:'
       subject: "%{name} compartilhou a sua postagem"
+      title: Novo compartilhamento
   number:
     human:
       decimal_units:
@@ -516,7 +548,9 @@ pt-BR:
           trillion: T
           unit: ''
   pagination:
+    newer: Mais novo
     next: Próximo
+    older: Mais antigo
     prev: Anterior
     truncate: "&hellip;"
   preferences:
@@ -551,12 +585,14 @@ pt-BR:
       blackberry: Blackberry
       chrome: Chrome
       edge: Microsoft Edge
+      electron: Electron
       firefox: Firefox
       generic: Navegador desconhecido
       ie: Internet Explorer
       micro_messenger: MicroMessenger
       nokia: Navegador Nokia S40 Ovi
       opera: Opera
+      otter: Otter
       phantom_js: PhantomJS
       qq: QQ Browser
       safari: Safari
@@ -617,7 +653,7 @@ pt-BR:
   stream_entries:
     click_to_show: Clique para mostrar
     pinned: Toot fixado
-    reblogged: compartilhado
+    reblogged: compartilhou
     sensitive_content: Conteúdo sensível
   terms:
     body_html: |
@@ -708,7 +744,31 @@ pt-BR:
     recovery_instructions_html: Se você perder acesso ao seu celular, você pode usar um dos códigos de recuperação abaixo para reganhar acesso à sua conta. <strong>Mantenha os códigos de recuperação em um local seguro</strong>. Por exemplo, você pode imprimi-los e guardá-los com outros documentos importantes.
     setup: Configurar
     wrong_code: O código inserido é invalido! O horário do servidor e o horário do seu aparelho estão corretos?
+  user_mailer:
+    backup_ready:
+      explanation: Você pediu um backup completo da sua conta no Mastodon. E agora está pronto para ser baixado!
+      subject: Seu arquivo está pronto para ser baixado
+      title: Arquivo "pra viagem"
+    welcome:
+      edit_profile_action: Configurar perfil
+      edit_profile_step: Você pode customizar o seu perfil enviando um avatar, uma imagem de topo, mudando seu nome de exibição, dentre outros. Se você gostaria de aprovar novos seguidores antes que eles possam seguir você, você pode trancar a sua conta.
+      explanation: Aqui estão algumas dicas para te ajudar a começar
+      final_action: Comece a postar
+      final_step: 'Comece a postar! Mesmo sem seguidores, suas mensagens públicas podem ser vistas por outros, por exemplo nas timelines locais e buscando hashtags. Você pode querer fazer uma introdução usando a hashtag #introduções, ou em inglês usando a hashtag #introductions.'
+      full_handle: Seu nome de usuário completo
+      full_handle_hint: Isso é o que você diz aos seus amigos para que eles possam te mandar mensagens ou te seguir a partir de outra instância.
+      review_preferences_action: Mudar as preferências
+      review_preferences_step: Não se esqueça de configurar suas preferências, como quais e-mails você gostaria de receber, que nível de privacidade você gostaria que seus posts tenham por padrão. Se você não sofre de enjôo com movimento, você pode habilitar GIFs animando automaticamente.
+      subject: Boas-vindas ao Mastodon
+      tip_bridge_html: Se você está vindo do Twitter, você pode encontrar pessoas conhecidas que estão no Mastodon usando <a href="%{bridge_url}">app de associação</a>. Mas só funciona se as pessoas também estiverem usando o app!
+      tip_federated_timeline: A timeline global é uma visão contínua da rede do Mastodon. Mas ela só inclui pessoas que outras pessoas da sua instância estão seguindo, então não é a rede completa.
+      tip_following: Você vai seguir administrador(es) da sua instância por padrão. Para encontrar mais gente interessante, confira as timelines local e global.
+      tip_local_timeline: A timeline local é uma visão contínua das pessoas que estão em %{instance}. Esses são seus vizinhos próximos!
+      tip_mobile_webapp: Se o seu navegador móvel oferecer a opção de adicionar Mastodon à tela inicial, você pode receber notificações push. Vai funcionar quase como um aplicativo nativo!
+      tips: Dicas
+      title: Boas-vindas à bordo, %{name}!
   users:
     invalid_email: O endereço de e-mail é inválido
     invalid_otp_token: Código de autenticação inválido
+    seamless_external_login: Você está logado usando um serviço externo, então configurações de e-mail e password não estão disponíveis.
     signed_in_as: 'Acesso como:'
diff --git a/config/locales/pt.yml b/config/locales/pt.yml
index 618373b91..5012e176f 100644
--- a/config/locales/pt.yml
+++ b/config/locales/pt.yml
@@ -23,7 +23,6 @@ pt:
       real_conversation_title: Feito para conversas reais
       within_reach_body: Várias aplicações para iOS, Android e outras plataformas graças a um ecossistema de API amigável para desenvolvedores, permitem-te que te mantenhas em contacto com os teus amigos em qualquer lugar.
       within_reach_title: Sempre ao teu alcance
-    find_another_instance: Encontra outra instância
     generic_description: "%{domain} é um servidor na rede"
     hosted_on: Mastodon em %{domain}
     learn_more: Saber mais
@@ -143,6 +142,7 @@ pt:
         demote_user: "%{name} rebaixou o utilizador %{target}"
         destroy_domain_block: "%{name} desbloqueou o domínio %{target}"
         destroy_email_domain_block: "%{name} adicionou na lista branca o domínio de correio electrónico %{target}"
+        destroy_status: "%{name} removeu o publicação feita por %{target}"
         disable_2fa_user: "%{name} desactivou o requerimento de autenticação em dois passos para o utilizador %{target}"
         disable_custom_emoji: "%{name} desabilitou o emoji %{target}"
         disable_user: "%{name} desativou o acesso para o utilizador %{target}"
@@ -248,6 +248,9 @@ pt:
       delete: Eliminar
       id: ID
       mark_as_resolved: Marcar como resolvido
+      nsfw:
+        'false': Mostrar imagens/vídeos
+        'true': Esconder imagens/vídeos
       report: 'Denúncia #%{id}'
       report_contents: Conteúdos
       reported_account: Conta denunciada
@@ -261,17 +264,25 @@ pt:
       unresolved: Por resolver
       view: Ver
     settings:
+      activity_api_enabled:
+        desc_html: Contagem semanais de publicações locais, utilizadores activos e novos registos
+        title: Publicar estatísticas agregadas sobre atividade dos utilizadores
       bootstrap_timeline_accounts:
+        desc_html: Separa os nomes de utilizadores por vírgulas. Funciona apenas com contas locais e desbloqueadas. O padrão quando vazio são todos os administradores locais.
         title: Seguidores predefinidos para novas contas
       contact_information:
         email: Inserir um endereço de email para tornar público
         username: Insira um nome de utilizador
+      peers_api_enabled:
+        desc_html: Nomes de domínio que esta instância encontrou no fediverso
+        title: Publicar lista de instâncias descobertas
       registrations:
         closed_message:
           desc_html: Mostrar na página inicial quando registos estão encerrados<br/>Podes usar tags HTML
           title: Mensagem de registos encerrados
         deletion:
           desc_html: Permite a qualquer um apagar a conta
+          title: Permitir eliminar contas
         min_invite_role:
           disabled: Ninguém
           title: Permitir convites de
@@ -326,9 +337,12 @@ pt:
       body: "%{reporter} relatou %{target}"
       subject: Novo relatório sobre %{instance} (#%{id})
   application_mailer:
+    notification_preferences: Alterar preferências de e-mail
     salutation: "%{name},"
     settings: 'Alterar preferências de email: %{link}'
     view: 'Ver:'
+    view_profile: Ver perfil
+    view_status: Ver publicação
   applications:
     created: Aplicação criada com sucesso
     destroyed: Aplicação eliminada com sucesso
@@ -339,7 +353,7 @@ pt:
     your_token: O teu token de acesso
   auth:
     agreement_html: Registando-te concordas em seguir <a href="%{rules_path}">as regras da instância</a> e <a href="%{terms_path}">os nossos termos de serviço</a>.
-    change_password: Alterar palavra-passe
+    confirm_email: Confirmar e-mail
     delete_account: Eliminar conta
     delete_account_html: Se desejas eliminar a conta, podes <a href="%{path}">continua aqui</a>. Uma confirmação será pedida.
     didnt_get_confirmation: Não recebeu o email de confirmação?
@@ -352,6 +366,7 @@ pt:
     register: Registar
     resend_confirmation: Reenviar instruções de confirmação
     reset_password: Criar nova palavra-passe
+    security: Alterar palavra-passe
     set_new_password: Editar palavra-passe
   authorize_follow:
     error: Infelizmente, ocorreu um erro ao buscar a conta remota
@@ -361,6 +376,7 @@ pt:
     post_follow:
       close: Ou podes simplesmente fechar esta janela.
       return: Voltar ao perfil do utilizador
+      web: Voltar à página inicial
     title: Seguir %{acct}
   datetime:
     distance_in_words:
@@ -393,6 +409,7 @@ pt:
     '422':
       content: "A verificação de segurança falhou. \nDesativaste o uso de cookies?"
       title: A verificação de segurança falhou
+    '429': Desacelerado
     '500':
       content: Desculpe, mas algo correu mal.
       title: Esta página não está correta
@@ -469,29 +486,38 @@ pt:
     title: Moderação
   notification_mailer:
     digest:
-      body: 'Aqui tens um breve resumo do que tens perdido em %{instance} desde o  último acesso em %{since}:'
+      action: Ver todas as notificações
+      body: Aqui tens um breve resumo do que perdeste desde o último acesso a %{since}
       mention: "%{name} mencionou-te em:"
       new_followers_summary:
-        one: Boa! Tens um novo seguidor!
+        one: Tens um novo seguidor! Boa!
         other: Tens %{count} novos seguidores! Fantástico!
       subject:
         one: "1 nova notificação desde o último acesso \U0001F418"
         other: "%{count} novas notificações desde o  último acesso \U0001F418"
+      title: Enquanto estiveste ausente…
     favourite:
       body: 'O teu post foi adicionado aos favoritos por %{name}:'
       subject: "%{name} adicionou o teu post aos favoritos"
+      title: Novo favorito
     follow:
       body: "%{name} é teu seguidor!"
       subject: "%{name} começou a seguir-te"
+      title: Novo seguidor
     follow_request:
+      action: Gerir pedidos de seguidores
       body: "%{name} solicitou autorização para te seguir"
       subject: 'Seguidor pendente: %{name}'
+      title: Nova solicitação de seguidor
     mention:
+      action: Responder
       body: 'Foste mencionado por %{name}:'
       subject: "%{name} mencionou-te"
+      title: Nova menção
     reblog:
       body: 'O teu post foi partilhado por %{name}:'
       subject: "%{name} partilhou o teu post"
+      title: Nova partilha
   number:
     human:
       decimal_units:
@@ -539,6 +565,7 @@ pt:
       blackberry: Blackberry
       chrome: Chrome
       edge: Microsoft Edge
+      electron: Electron
       firefox: Firefox
       generic: Navegador desconhecido
       ie: Internet Explorer
@@ -589,8 +616,10 @@ pt:
     open_in_web: Abrir no browser
     over_character_limit: limite de caracter excedeu %{max}
     pin_errors:
+      limit: Já fixaste a quantidade máxima de publicações
       ownership: Posts de outras pessoas não podem ser fixados
       private: Post não-público não pode ser fixado
+      reblog: Não podes fixar uma partilha
     show_more: Mostrar mais
     title: '%{name}: "%{quote}"'
     visibilities:
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 842fd7d54..a2cb1e793 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -23,7 +23,6 @@ ru:
       real_conversation_title: Создан для настоящего общения
       within_reach_body: Различные приложения для iOS, Android и других платформ, написанные благодаря дружественной к разработчикам экосистеме API, позволят Вам держать связь с Вашими друзьями где угодно.
       within_reach_title: Всегда под рукой
-    find_another_instance: Найти другой узел
     generic_description: "%{domain} - один из серверов сети"
     hosted_on: Mastodon размещен на %{domain}
     learn_more: Узнать больше
@@ -354,7 +353,6 @@ ru:
     your_token: Ваш токен доступа
   auth:
     agreement_html: Создавая аккаунт, вы соглашаетесь с <a href="%{rules_path}">правилами узла</a> и <a href="%{terms_path}">нашими условиями обслуживания</a>.
-    change_password: Изменить пароль
     delete_account: Удалить аккаунт
     delete_account_html: Если Вы хотите удалить свой аккаунт, вы можете <a href="%{path}">перейти сюда</a>. У Вас будет запрошено подтверждение.
     didnt_get_confirmation: Не получили инструкцию для подтверждения?
@@ -367,6 +365,7 @@ ru:
     register: Зарегистрироваться
     resend_confirmation: Повторить отправку инструкции для подтверждения
     reset_password: Сбросить пароль
+    security: Изменить пароль
     set_new_password: Задать новый пароль
   authorize_follow:
     error: К сожалению, при поиске удаленного аккаунта возникла ошибка
diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml
index e3876e47b..414f0c342 100644
--- a/config/locales/simple_form.ar.yml
+++ b/config/locales/simple_form.ar.yml
@@ -3,65 +3,63 @@ ar:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF أو JPG. على الأكثر 2 ميغابيت . سوف يتم تصغيرها إلى 120x120px
-        digest: يُرسَل بعد مضيّ مدة طويلة من خمول نشاطك يحوي على تلخيص للتبويقات التي ذُكر حسابك فيها أثناء غيابك
-        display_name:
-          one: <span class="name-counter">1</span> حرف متبقي
-          other: <span class="name-counter">%{count}</span> حرف متبقي
-        header: PNG, GIF or JPG. على الأكثر 2 ميغابيت . سوف يتم تصغيرها إلى 700x335px
-        locked: يتطلب منك الموافقة يدويا على كل طلب للإشتراك بحسابك و منشوراتك تعرض لمتابعيك فقط دون غيرهم
-        note: <span class="note-counter">%{count}</span> أحرف متبقية
-        setting_noindex: تمس ملفك العمومي الخاص بك وصفحات الحالة
-        setting_theme: تغير المظهر الذي يبدو عليه ماستدون عندما تقوم بتسجيل دخولك على أي جهاز.
+        avatar: ملف PNG أو GIF أو JPG. حجمه على أقصى تصدير 2MB. سيتم تصغيره إلى 400x400px
+        digest: تُرسَل إليك بعد مُضيّ مدة مِن خمول نشاطك و فقط إذا ما تلقيت رسائل شخصية مباشِرة أثناء فترة غيابك مِن الشبكة
+        header: ملف PNG أو GIF أو JPG. حجمه على أقصى تصدير 2MB. سيتم تصغيره إلى 700x335px
+        locked: يتطلب منك الموافقة يدويا على طلبات المتابعة
+        setting_noindex: ذلك يؤثر على حالة ملفك الشخصي و صفحاتك
+        setting_theme: ذلك يؤثر على الشكل الذي سيبدو عليه ماستدون عندما تقوم بالدخول مِن أي جهاز.
       imports:
-        data: ملف CSV تم تصديره من خادوم مثيل  آخر لماستدون
+        data: ملف CSV تم تصديره مِن مثيل خادوم ماستدون آخر
       sessions:
-        otp: أدخل الرمز الثنائي من هاتفك أو استخدم أحد رموز الاسترداد.
+        otp: قم بإدخال رمز المصادقة بخطوتين مِن هاتفك أو إستخدم أحد رموز النفاذ الإحتياطية.
       user:
-        filtered_languages: سوف لن تَظهَر التبويقات المُحرَّرَة باللغات المُحدّدة أدناه على خيوطك العمومية
+        filtered_languages: سوف يتم تصفية و إخفاء اللغات المختارة من خيوطك العمومية
     labels:
       defaults:
         avatar: الصورة الرمزية
-        confirm_new_password: تأكيد كلمة المرور الجديدة
-        confirm_password: تأكيد كلمة المرور
-        current_password: كلمة المرور الحالية
+        confirm_new_password: تأكيد كلمة السر الجديدة
+        confirm_password: تأكيد كلمة السر
+        current_password: كلمة السر الحالية
         data: البيانات
-        display_name: الاسم الذي يتم عرضه
+        display_name: الإسم المعروض
         email: عنوان البريد الإلكتروني
-        expires_in: مدة نهاية الصلاحية
-        filtered_languages: اللغات المصفاة
-        header: رأس الصفحة
+        expires_in: تنتهي مدة صلاحيته بعد
+        filtered_languages: اللغات التي تم تصفيتها
+        header: الرأسية
         locale: اللغة
-        locked: إجعل حسابك خاصًا
-        max_uses: العدد الأقصى للإستخدام
-        new_password: كلمة مرور جديدة
+        locked: تجميد الحساب
+        max_uses: عدد مرات استخدام الرابط
+        new_password: كلمة السر الجديدة
         note: السيرة الذاتية
-        otp_attempt: الرمز الثنائي
-        password: كلمة المرور
+        otp_attempt: رمز المصادقة بخطوتين
+        password: كلمة السر
         setting_auto_play_gif: تشغيل تلقائي لِوَسائط جيف المتحركة
-        setting_boost_modal: إظهار مربع حوار التأكيد قبل القيام بالترقية
+        setting_boost_modal: إظهار مربع حوار للتأكيد قبل ترقية أي تبويق
         setting_default_privacy: خصوصية المنشور
-        setting_default_sensitive: دائما تحديد الوسائط كحساسة
+        setting_default_sensitive: إعتبر الوسائط دائما كمحتوى حساس
         setting_delete_modal: إظهار مربع حوار للتأكيد قبل حذف أي تبويق
-        setting_noindex: منع محركات البحث من فهرسة ملفي الشخصي
+        setting_display_sensitive_media: دائمًا إظهار الوسائط الحساسة
+        setting_noindex: عدم السماح لمحركات البحث بفهرسة ملفك الشخصي
         setting_reduce_motion: تخفيض عدد الصور في الوسائط المتحركة
-        setting_system_font_ui: إستخدام الخط الإفتراضي للنظام
+        setting_system_font_ui: إستخدم الخطوط الإفتراضية للنظام
         setting_theme: سمة الموقع
         setting_unfollow_modal: إظهار مربع حوار للتأكيد قبل إلغاء متابعة أي حساب
-        severity: الشدة
-        type: نوع الإستيراد
-        username: اسم المستخدم
+        severity: القوّة
+        type: صيغة الإستيراد
+        username: إسم المستخدم
+        username_or_email: إسم المستخدم أو كلمة السر
       interactions:
-        must_be_follower: حظر اشعارات الأشخاص الذين لا يتبعونك
-        must_be_following: حظر اشعارات الأشخاص الذين لا تتبعهم
-        must_be_following_dm: حظر التبويقات المباشرة القادمة من الحسابات التي لا تتبعها
+        must_be_follower: حظر الإخطارات القادمة من حسابات لا تتبعك
+        must_be_following: حظر الإخطارات القادمة من الحسابات التي لا تتابعها
+        must_be_following_dm: حظر الرسائل المباشرة القادمة من طرف أشخاص لا تتبعهم
       notification_emails:
-        digest: إرسال رسائل بريد إلكتروني ملخصة
-        favourite: إبعث بريداً إلكترونياً عندما يعجب احدهم بمنشورك
-        follow: إبعث بريداً إلكترونياً عندما يتبعك أحد
-        follow_request: إبعث بريداً إلكترونياً عند تلقيك طلب بالمتابعة
-        mention: إبعث بريداً إلكترونياً عندما يشار إليك
-        reblog: إبعث بريداً إلكترونياً عندما يُرقّي احدهم منشورك
+        digest: إرسال ملخصات عبر البريد الإلكتروني
+        favourite: إبعث بريداً إلكترونيًا عندما يُعجَب أحدهم بمنشورك
+        follow: إبعث بريداً إلكترونيًا عندما يتبعك أحد
+        follow_request: إبعث بريدا إلكترونيا عندما يقوم أحدهم بإرسال طلب بالمتابعة
+        mention: إبعث بريداً إلكترونيًا عندما يُشير إليك أو يذكُرك أحدهم
+        reblog: إبعث بريداً إلكترونيًا عندما يقوم أحدهم بترقية منشورك
     'no': لا
     required:
       mark: "*"
diff --git a/config/locales/simple_form.bg.yml b/config/locales/simple_form.bg.yml
index 2ea70d51c..720a8391d 100644
--- a/config/locales/simple_form.bg.yml
+++ b/config/locales/simple_form.bg.yml
@@ -3,7 +3,7 @@ bg:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF или JPG. До 2MB. Ще бъде смалена до 120x120 пиксела
+        avatar: PNG, GIF или JPG. До 2MB. Ще бъде смалена до 400x400 пиксела
         display_name: До 30 символа
         header: PNG, GIF или JPG. До 2MB. Ще бъде смалена до 700x335 пиксела
         locked: Изисква ръчно одобрение на последователите. По подразбиране, публикациите са достъпни само до последователи.
diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml
index 3d210b73c..300da45a5 100644
--- a/config/locales/simple_form.ca.yml
+++ b/config/locales/simple_form.ca.yml
@@ -3,67 +3,69 @@ ca:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF o JPG. Màxim 2MB. Serà escalat a 120x120px
+        avatar: PNG, GIF o JPG. Màxim 2MB. S'escalarà a 400x400px
         digest: Només s'envia després d'un llarg període d'inactivitat amb un resum de les mencions que has rebut en la teva absència
         display_name:
-          one: <span class="name-counter">1</span> càracter
-          other: <span class="name-counter">%{count}</span> càracters
-        header: PNG, GIF o JPG. Màxim 2MB. Serà escalat a 700x335px
-        locked: Requereix que aprovis manualment seguidors i les publicacions seran mostrades només als teus seguidors
+          one: <span class="name-counter">1</span> càracter restant
+          other: <span class="name-counter">%{count}</span> càracters restans
+        header: PNG, GIF o JPG. Màxim 2MB. S'escalarà a 700x335px
+        locked: Requereix que aprovis manualment els seguidors
         note:
-          one: <span class="note-counter">1</span> càracter left
-          other: <span class="note-counter">%{count}</span> càracters
+          one: <span class="note-counter">1</span> càracter restant
+          other: <span class="note-counter">%{count}</span> caràcters restants
         setting_noindex: Afecta el teu perfil públic i les pàgines d'estat
-        setting_theme: Afecta la manera en què Mastodon es veu quan està connectat des de qualsevol dispositiu.
+        setting_theme: Afecta l'aspecte de Mastodon quan es visita des de qualsevol dispositiu.
       imports:
-        data: Arxiu CSV exportat desde una altra instància de Mastodon
+        data: Fitxer CSV exportat des de una altra instància de Mastodon
       sessions:
         otp: Introdueix el codi de dos factors des del teu telèfon o utilitza un dels teus codis de recuperació.
       user:
-        filtered_languages: Els idiomes seleccionats seran eliminats de les línies de temps públiques
+        filtered_languages: Les llengües seleccionades s'eliminaran de les línies de temps públiques
     labels:
       defaults:
         avatar: Avatar
-        confirm_new_password: Confirmar nova contrasenya
-        confirm_password: Confirmar contrasenya
+        confirm_new_password: Confirma la contrasenya nova
+        confirm_password: Confirma la contrasenya
         current_password: Contrasenya actual
         data: Informació
-        display_name: Mostrar nom
-        email: Direcció de correu electrònic
-        expires_in: Caduca després
-        filtered_languages: Idiomes filtrats
-        header: Img. capçalera
-        locale: Idioma
-        locked: Fer privat aquest compte
+        display_name: Nom visible
+        email: Adreça de correu electrònic
+        expires_in: Expira després
+        filtered_languages: Llengües filtrades
+        header: Capçalera
+        locale: Llengua
+        locked: Fes aquest compte privat
         max_uses: Nombre màxim d'usos
-        new_password: Nova contrasenya
+        new_password: Contrasenya nova
         note: Biografia
         otp_attempt: Codi de dos factors
         password: Contrasenya
-        setting_auto_play_gif: Auto-reproducció de GIFs animats
-        setting_boost_modal: Mostrar finestra de confirmació abans d'un Retoot
-        setting_default_privacy: Privacitat de publicacions
-        setting_default_sensitive: Marca sempre els multimèdia com a sensibles
-        setting_delete_modal: Mostrar finestra de confirmació abans d'esborrar un toot
+        setting_auto_play_gif: Reproducció automàtica de GIFs animats
+        setting_boost_modal: Mostra la finestra de confirmació abans d'un retoot
+        setting_default_privacy: Privacitat de les publicacions
+        setting_default_sensitive: Marca sempre els elements multimèdia com a sensibles
+        setting_delete_modal: Mostra la finestra de confirmació abans de suprimir un toot
+        setting_display_sensitive_media: Mostra sempre els elements multimèdia marcats com a sensibles
         setting_noindex: Desactivació de la indexació del motor de cerca
         setting_reduce_motion: Redueix el moviment en animacions
-        setting_system_font_ui: Utilitzeu el tipus de lletra predeterminat del sistema
+        setting_system_font_ui: Utilitza el tipus de lletra predeterminat del sistema
         setting_theme: Tema del lloc
         setting_unfollow_modal: Mostra el diàleg de confirmació abans de deixar de seguir a algú
         severity: Severitat
-        type: Importar tipus
-        username: Nom d´usuari
+        type: Importa el tipus
+        username: Nom d'usuari
+        username_or_email: Nom d'usuari o adreça electrònica
       interactions:
-        must_be_follower: Bloquejar notificacions de persones que no et segueixen
-        must_be_following: Bloquejar notificacions de persones que no segueixes
-        must_be_following_dm: Bloqueja missatges directes de persones que no segueixes
+        must_be_follower: Blocar les notificacions de persones que no et segueixen
+        must_be_following: Bloca les notificacions de persones que no segueixes
+        must_be_following_dm: Bloca els missatges directes de persones que no segueixes
       notification_emails:
-        digest: Enviar resum de correus electrònics
-        favourite: Enviar correu electrònic quan algú marqui com a favorit en la teva publicació
-        follow: Enviar correu electrònic quan algú et segueixi
-        follow_request: Enviar correu electrònic quan algú sol·liciti seguir-te
-        mention: Enviar correu electrònic quan algú et mencioni
-        reblog: Enviar correu electrònic quan algú comparteixi la seva publicació
+        digest: Envia un resum per correu electrònic
+        favourite: Envia un correu electrònic si algú marca com a preferit el teu estat
+        follow: Envia un correu electrònic si algú et segueix
+        follow_request: Envia un correu electrònic si algú sol·licita seguir-te
+        mention: Envia un correu electrònic si algú et menciona
+        reblog: Envia un correu electrònic si algú comparteix el teu estat
     'no': 'No'
     required:
       mark: "*"
diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml
index 3c5e467a2..5a65173be 100644
--- a/config/locales/simple_form.de.yml
+++ b/config/locales/simple_form.de.yml
@@ -3,7 +3,7 @@ de:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF oder JPG. Maximal 2 MB. Wird auf 120×120 px herunterskaliert
+        avatar: PNG, GIF oder JPG. Maximal 2 MB. Wird auf 400×400 px herunterskaliert
         digest: Wenn du lange Zeit inaktiv bist, wird dir eine Zusammenfassung von Erwähnungen in deiner Abwesenheit zugeschickt
         display_name:
           one: <span class="name-counter">1</span> Zeichen verbleibt
@@ -45,6 +45,7 @@ de:
         setting_default_privacy: Beitragssichtbarkeit
         setting_default_sensitive: Medien immer als heikel markieren
         setting_delete_modal: Bestätigungsdialog anzeigen, bevor ein Beitrag gelöscht wird
+        setting_display_sensitive_media: Medien, die als heikel markiert sind, immer anzeigen
         setting_noindex: Suchmaschinen-Indexierung verhindern
         setting_reduce_motion: Bewegung in Animationen verringern
         setting_system_font_ui: Standardschriftart des Systems verwenden
@@ -53,6 +54,7 @@ de:
         severity: Gewichtung
         type: Importtyp
         username: Profilname
+        username_or_email: Profilname oder Email
       interactions:
         must_be_follower: Benachrichtigungen von Nicht-Folgenden blockieren
         must_be_following: Benachrichtigungen von Profilen blockieren, denen ich nicht folge
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index ceb015282..7ffa59f46 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -3,7 +3,7 @@ en:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF or JPG. At most 2MB. Will be downscaled to 120x120px
+        avatar: PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px
         digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
         display_name:
           one: <span class="name-counter">1</span> character left
@@ -45,6 +45,7 @@ en:
         setting_default_privacy: Post privacy
         setting_default_sensitive: Always mark media as sensitive
         setting_delete_modal: Show confirmation dialog before deleting a toot
+        setting_display_sensitive_media: Always show media marked as sensitive
         setting_favourite_modal: Show confirmation dialog before favouriting
         setting_noindex: Opt-out of search engine indexing
         setting_reduce_motion: Reduce motion in animations
@@ -54,6 +55,7 @@ en:
         severity: Severity
         type: Import type
         username: Username
+        username_or_email: Username or Email
       interactions:
         must_be_follower: Block notifications from non-followers
         must_be_following: Block notifications from people you don't follow
diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml
index a3824d349..17862f916 100644
--- a/config/locales/simple_form.eo.yml
+++ b/config/locales/simple_form.eo.yml
@@ -3,42 +3,69 @@ eo:
   simple_form:
     hints:
       defaults:
-        avatar: En la formato PNG, GIF aŭ JPG. Ĝis 2Mo. Estos malgrandigita al 120x120px
-        display_name: 30 signoj pleje
-        header: En la formato PNG, GIF aŭ JPG. Ĝis 2Mo. Estos malgrandigita al 700x335px
-        locked: Vi devos aprobi ĉiun peton de sekvado, kaj viaj mesaĝoj estos senŝanĝe nur por viaj sekvantoj.
-        note: 160 signoj pleje
+        avatar: Formato PNG, GIF aŭ JPG. Ĝis 2MB. Estos malgrandigita al 400x400px
+        digest: Sendita nur post longa tempo de neaktiveco, kaj nur se vi ricevis personan mesaĝon en via foresto
+        display_name:
+          one: <span class="name-counter">1</span> signo restas
+          other: <span class="name-counter">%{count}</span> signoj restas
+        header: Formato PNG, GIF aŭ JPG. Ĝis 2MB. Estos malgrandigita al 700x335px
+        locked: Vi devos aprobi ĉiun peton de sekvado mane
+        note:
+          one: <span class="note-counter">1</span> signo restas
+          other: <span class="note-counter">%{count}</span> signoj restas
+        setting_noindex: Influas vian publikan profilon kaj mesaĝajn paĝojn
+        setting_theme: Influas kiel Mastodon aspektas kiam vi ensalutis en ajna aparato.
       imports:
-        data: Dosiero CSV el alia aperaĵo de Mastodon
+        data: CSV-dosiero el alia nodo de Mastodon
+      sessions:
+        otp: Enmetu la kodon de dufaktora aŭtentigo el via telefono aŭ uzu unu el la realiraj kodoj.
+      user:
+        filtered_languages: Markitaj lingvoj estos elfiltritaj de publikaj tempolinioj por vi
     labels:
       defaults:
         avatar: Profilbildo
         confirm_new_password: Konfirmi novan pasvorton
-        confirm_password: Konfirmi la pasvorton
+        confirm_password: Konfirmi pasvorton
         current_password: Nuna pasvorto
         data: Datumoj
         display_name: Publika nomo
-        email: Retpoŝt-adreso
-        header: Kapbildo
+        email: Retadreso
+        expires_in: Eksvalidiĝas post
+        filtered_languages: Filtritaj lingvoj
+        header: Fonbildo
         locale: Lingvo
-        locked: Privatigi la konton
+        locked: Ŝlosi konton
+        max_uses: Maksimuma nombro de uzoj
         new_password: Nova pasvorto
         note: Sinprezento
-        otp_attempt: Dufaktora identigilo
+        otp_attempt: Kodo de dufaktora aŭtentigo
         password: Pasvorto
-        setting_default_privacy: Videbleco de la mesaĝoj
-        type: Tipo de alportado
+        setting_auto_play_gif: Aŭtomate ekigi GIF-ojn
+        setting_boost_modal: Montri fenestron por konfirmi antaŭ ol diskonigi
+        setting_default_privacy: Mesaĝa videbleco
+        setting_default_sensitive: Ĉiam marki aŭdovidaĵojn tiklaj
+        setting_delete_modal: Montri fenestron por konfirmi antaŭ ol forigi mesaĝon
+        setting_display_sensitive_media: Ĉiam montri aŭdovidaĵon markitajn tiklaj
+        setting_noindex: Ellistiĝi de retserĉila indeksado
+        setting_reduce_motion: Malrapidigi animaciojn
+        setting_system_font_ui: Uzi la dekomencan tiparon de la sistemo
+        setting_theme: Reteja etoso
+        setting_unfollow_modal: Montri fenestron por konfirmi antaŭ ol ĉesi sekvi iun
+        severity: Graveco
+        type: Importa tipo
         username: Uzantnomo
+        username_or_email: Uzantnomo aŭ Retadreso
       interactions:
-        must_be_follower: Kaŝi la sciigojn de homoj, kiuj ne sekvas vin
-        must_be_following: Kaŝi la sciigojn de homoj, kiujn vi ne sekvas
+        must_be_follower: Bloki sciigojn de nesekvantoj
+        must_be_following: Bloki sciigojn de homoj, kiujn vi ne sekvas
+        must_be_following_dm: Bloki rektajn mesaĝojn de homoj, kiujn vi ne sekvas
       notification_emails:
-        digest: Sendi resumajn retpoŝt-mesaĝojn
-        favourite: Sendi retpoŝt-mesaĝon, kiam iu favoras mesaĝon de vi
-        follow: Sendi retpoŝt-mesaĝon, kiam iu eksekvas vin
-        follow_request: Sendi retpoŝt-mesaĝon, kiam iu petas sekvi vin
-        mention: Sendi retpoŝt-mesaĝon, kiam iu mencias vin
-        reblog: Sendi retpoŝt-mesaĝon, kiam iu diskonigas mesaĝon de vi
+        digest: Sendi resumajn retmesaĝojn
+        favourite: Sendi retmesaĝon kiam iu stelumas vian mesaĝon
+        follow: Sendi retmesaĝon kiam iu sekvas vin
+        follow_request: Sendi retmesaĝon kiam iu petas sekvi vin
+        mention: Sendi retmesaĝon kiam iu mencias vin
+        reblog: Sendi retmesaĝon kiam iu diskonigas vian mesaĝon
     'no': Ne
     required:
       mark: "*"
diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml
index 01fe2af4c..5d6a9e3d2 100644
--- a/config/locales/simple_form.es.yml
+++ b/config/locales/simple_form.es.yml
@@ -3,8 +3,8 @@ es:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF o JPG. Máximo 2MB. Será escalado a 120x120px
-        digest: Enviado tras un largo periodo de inactividad, con un resumen de las menciones que has recibido durante tu ausencia
+        avatar: PNG, GIF o JPG. Máximo 2MB. Será escalado a 400x400px
+        digest: Solo enviado tras un largo periodo de inactividad y solo si usted ha recibido mensajes personales durante su ausencia
         display_name:
           one: <span class="name-counter">1</span> caracter restante
           other: <span class="name-counter">%{count}</span> caracteres restantes
diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml
index 8ca4378b1..f692cd780 100644
--- a/config/locales/simple_form.fi.yml
+++ b/config/locales/simple_form.fi.yml
@@ -3,7 +3,7 @@ fi:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 120x120px
+        avatar: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 400x400px
         display_name: Korkeintaan 30 merkkiä
         header: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 700x335px
         locked: Vaatii sinun manuaalisesti hyväksymään seuraajat ja asettaa julkaisujen yksityisyyden vain seuraajille
diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml
index 017aa4021..71674199d 100644
--- a/config/locales/simple_form.fr.yml
+++ b/config/locales/simple_form.fr.yml
@@ -3,7 +3,7 @@ fr:
   simple_form:
     hints:
       defaults:
-        avatar: Au format PNG, GIF ou JPG. 2 Mo maximum. Sera réduit à 120x120px
+        avatar: Au format PNG, GIF ou JPG. 2 Mo maximum. Sera réduit à 400x400px
         digest: Uniquement envoyé après une longue période d’inactivité et uniquement si vous avez reçu des messages personnels pendant votre absence
         display_name:
           one: <span class="name-counter">1</span> caractère restant
@@ -45,14 +45,16 @@ fr:
         setting_default_privacy: Confidentialité des statuts
         setting_default_sensitive: Toujours marquer les médias comme sensibles
         setting_delete_modal: Afficher une fenêtre de confirmation avant de supprimer un pouet
+        setting_display_sensitive_media: Toujours afficher les médias marqués comme sensibles
         setting_noindex: Demander aux moteurs de recherche de ne pas indexer vos informations personnelles
         setting_reduce_motion: Réduire la vitesse des animations
         setting_system_font_ui: Utiliser la police par défaut du système
         setting_theme: Thème du site
         setting_unfollow_modal: Afficher une fenêtre de confirmation avant de vous désabonner d’un compte
-        severity: Séverité
+        severity: Sévérité
         type: Type d’import
         username: Identifiant
+        username_or_email: Nom d'utilisateur ou courriel
       interactions:
         must_be_follower: Masquer les notifications des personnes qui ne vous suivent pas
         must_be_following: Masquer les notifications des personnes que vous ne suivez pas
diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml
index d7e5601f2..4dcdd0459 100644
--- a/config/locales/simple_form.gl.yml
+++ b/config/locales/simple_form.gl.yml
@@ -3,8 +3,8 @@ gl:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF ou JPG. Como moito 2MB. Será reducida ate 120x120px
-        digest: Enviar despois de un período longo de inactividade con un resumo das mencións que recibeu na súa ausencia
+        avatar: PNG, GIF ou JPG. Como moito 2MB. Será reducida ate 400x400px
+        digest: Enviar só tras un longo período de inactividade e só si recibeu algunha mensaxe personal na súa ausencia
         display_name:
           one: <span class="name-counter">1</span> caracter restante
           other: <span class="name-counter">%{count}</span> caracteres restantes
@@ -45,6 +45,7 @@ gl:
         setting_default_privacy: Intimidade da publicación
         setting_default_sensitive: Marcar sempre multimedia como sensible
         setting_delete_modal: Solicitar confirmación antes de eliminar unha mensaxe
+        setting_display_sensitive_media: Mostrar sempre os medios marcados como sensibles
         setting_noindex: Pedir non aparecer nas buscas dos motores de busca
         setting_reduce_motion: Reducir o movemento nas animacións
         setting_system_font_ui: Utilizar a tipografía por defecto do sistema
@@ -53,6 +54,7 @@ gl:
         severity: Severidade
         type: Tipo de importación
         username: Nome de usuaria
+        username_or_email: Nome de usuaria ou Correo-e
       interactions:
         must_be_follower: Bloquear as notificacións de non-seguidoras
         must_be_following: Bloquea as notificacións de personas que non segue
diff --git a/config/locales/simple_form.he.yml b/config/locales/simple_form.he.yml
index 604236a73..20cf1029e 100644
--- a/config/locales/simple_form.he.yml
+++ b/config/locales/simple_form.he.yml
@@ -3,7 +3,7 @@ he:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF או JPG. מקסימום 2MB. גודל התמונה יוקטן ל-120x120px
+        avatar: PNG, GIF או JPG. מקסימום 2MB. גודל התמונה יוקטן ל-400x400px
         digest: נשלח לאחר תקופה ארוכה של אי-פעילות עם סיכום איזכורים שקיבלת בהעדרך
         display_name:
           one: נותרה אות<span class="name-counter">אחת</span>
diff --git a/config/locales/simple_form.hr.yml b/config/locales/simple_form.hr.yml
index 3010423a7..2506c8d92 100644
--- a/config/locales/simple_form.hr.yml
+++ b/config/locales/simple_form.hr.yml
@@ -3,7 +3,7 @@ hr:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF ili JPG. Najviše 2MB. Bit će smanjen na 120x120px
+        avatar: PNG, GIF ili JPG. Najviše 2MB. Bit će smanjen na 400x400px
         display_name: Najviše 30 znakova
         header: PNG, GIF ili JPG. Najviše 2MB. Bit će smanjen na 700x335px
         locked: traži te da ručno odobriš sljedbenike i postavlja privatnost postova na dostupnu samo sljedbenicima
diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml
index c4dc3aead..6ec39aa79 100644
--- a/config/locales/simple_form.hu.yml
+++ b/config/locales/simple_form.hu.yml
@@ -1,23 +1,67 @@
 ---
 hu:
   simple_form:
+    hints:
+      defaults:
+        avatar: PNG, GIF vagy JPG. Maximum 2MB. Át lesz méretezve 400x400 pixelre
+        digest: Csak hosszú távollét esetén küldve és csak ha személyes üzenetet kaptál távollétedben
+        display_name:
+          one: <span class="name-counter">1</span>karakter maradt
+          other: <span class="name-counter">%{count}</span>karakter maradt
+        header: PNG, GIF vagy JPG. Maximum 2MB. Át lesz méretezve 700x335 pixelre
+        locked: Egyenként engedélyezned kell a követőidet
+        note:
+          one: <span class="note-counter">1</span>karakter maradt
+          other: <span class="note-counter">%{count}</span>karakter maradt
+        setting_noindex: A publikus profilodra és státusz oldalra vonatkozik
+        setting_theme: A bármely eszközről bejelentkezett felület kinézetére vonatkozik.
+      imports:
+        data: Egy másik Mastodon szerverről exportált CSV fájl
+      sessions:
+        otp: Add meg a Második-faktor kódodat a telefonodról vagy használd az egyik tartalék bejelentkező kódodat.
+      user:
+        filtered_languages: A kiválasztott nyelvek nem jelennek majd meg a nyilvános idővonaladon
     labels:
       defaults:
         avatar: Profilkép
         confirm_new_password: Új jelszó megerősítése
         confirm_password: Jelszó megerősítése
         current_password: Jelenlegi jelszó
+        data: Adatok
         display_name: Megjelenített név
         email: E-mail cím
+        expires_in: Elévül
+        filtered_languages: Szűrt nyelvek
         header: Fejléc
         locale: Nyelv
+        locked: Zárt felhasználói fiók
+        max_uses: Felhasználhatóság
         new_password: Új jelszó
         note: Önéletrajz
+        otp_attempt: Második-faktor kód
         password: Jelszó
+        setting_auto_play_gif: GIF-ek automatikus lejátszása
+        setting_boost_modal: Megerősítés kérése reblogolás előtt
+        setting_default_privacy: Tülkök alapártelmezett adatvédelmi szintje
+        setting_default_sensitive: Minden médiafájl megjelölése szenzitívként
+        setting_delete_modal: Megerősítés kérése tülk törlése előtt
+        setting_noindex: Megtiltom a keresőmotoroknak, hogy indexeljék a tülkjeimet
+        setting_reduce_motion: Animációk mozgásának csökkentése
+        setting_system_font_ui: Rendszer betűtípusának használata
+        setting_theme: Oldalsablon
+        setting_unfollow_modal: Megerősítés kérése mielőtt abbahagyod valaki követését
+        severity: Súlyosság
+        type: Importálás típusa
         username: Felhasználónév
+      interactions:
+        must_be_follower: Nem követőidtől érkező értesítések tiltása
+        must_be_following: Nem követettjeidtől érkező értesítések tiltása
+        must_be_following_dm: Nem követettjeidtől érkező üzenetek tiltása
       notification_emails:
+        digest: Összevont e-mailek küldése
         favourite: E-mail küldése amikor valaki kedvencnek jelöli az állapotod
         follow: E-mail küldése amikor valaki követni kezd téged
+        follow_request: E-mail küldése amikor valaki követni szeretne téged
         mention: E-mail küldése amikor valaki megemlít téged
         reblog: E-mail küldése amikor valaki reblogolja az állapotod
     'no': Nem
diff --git a/config/locales/simple_form.id.yml b/config/locales/simple_form.id.yml
index b00b4ef4f..6b2868e02 100644
--- a/config/locales/simple_form.id.yml
+++ b/config/locales/simple_form.id.yml
@@ -3,7 +3,7 @@ id:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF atau JPG. Maksimal 2MB. Ukuran dikecilkan menjadi 120x120px
+        avatar: PNG, GIF atau JPG. Maksimal 2MB. Ukuran dikecilkan menjadi 400x400px
         display_name: Maksimal 30 karakter
         header: PNG, GIF atau JPG. Maksimal 2MB. Ukuran dikecilkan menjadi 700x335px
         locked: Anda harus menerima permintaan pengikut secara manual dan setting privasi postingan akan diubah khusus untuk pengikut
diff --git a/config/locales/simple_form.io.yml b/config/locales/simple_form.io.yml
index 92c19ded2..60b578a12 100644
--- a/config/locales/simple_form.io.yml
+++ b/config/locales/simple_form.io.yml
@@ -3,7 +3,7 @@ io:
   simple_form:
     hints:
       defaults:
-        avatar: En la formato PNG, GIF o JPG. Til 2Mo. Esos mikrigita a 120x120px
+        avatar: En la formato PNG, GIF o JPG. Til 2Mo. Esos mikrigita a 400x400px
         display_name: 30 signi maxime
         header: En la formato PNG, GIF o JPG. Til 2Mo. Esos mikrigita a 700x335px
         locked: Tu devos aprobar omna demandi di sequado, e tua mesaji esos senchanje nur por tua sequanti.
diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml
index af6f30e61..b2fcef109 100644
--- a/config/locales/simple_form.it.yml
+++ b/config/locales/simple_form.it.yml
@@ -3,7 +3,7 @@ it:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF o JPG. Al massimo 2MB. Sarà ridotto a 120x120px
+        avatar: PNG, GIF o JPG. Al massimo 2MB. Sarà ridotto a 400x400px
         display_name: Al massimo 30 characters
         header: PNG, GIF or JPG. Al massimo 2MB. Sarà ridotto a 700x335px
         locked: Richiede la tua approvazione per i nuovi seguaci e rende i nuovi post automaticamente visibili solo ai seguaci
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index 33b75e74a..d11430338 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -3,7 +3,7 @@ ja:
   simple_form:
     hints:
       defaults:
-        avatar: 2MBまでのPNGやGIF、JPGが利用可能です。120x120pxまで縮小されます
+        avatar: 2MBまでのPNGやGIF、JPGが利用可能です。400x400pxまで縮小されます
         digest: 長期間使用していない場合と不在時に返信を受けた場合のみ送信されます
         display_name: あと<span class="name-counter">%{count}</span>文字入力できます。
         header: 2MBまでのPNGやGIF、JPGが利用可能です。 700x335pxまで縮小されます
@@ -41,6 +41,7 @@ ja:
         setting_default_privacy: 投稿の公開範囲
         setting_default_sensitive: メディアを常に閲覧注意としてマークする
         setting_delete_modal: トゥートを削除する前に確認ダイアログを表示する
+        setting_display_sensitive_media: 閲覧注意としてマークされたメディアも常に表示する
         setting_favourite_modal: お気に入りをする前に確認ダイアログを表示する
         setting_noindex: 検索エンジンによるインデックスを拒否する
         setting_reduce_motion: アニメーションの動きを減らす
@@ -50,6 +51,7 @@ ja:
         severity: 重大性
         type: インポートする項目
         username: ユーザー名
+        username_or_email: ユーザー名またはメールアドレス
       interactions:
         must_be_follower: フォロワー以外からの通知をブロック
         must_be_following: フォローしていないユーザーからの通知をブロック
diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml
index 48306f418..e49bfd9f9 100644
--- a/config/locales/simple_form.ko.yml
+++ b/config/locales/simple_form.ko.yml
@@ -3,8 +3,8 @@ ko:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF 혹은 JPG. 최대 2MB. 120x120px로 다운스케일 됨
-        digest: 오랫동안 활동하지 않았을 때 받은 멘션들에 대한 요약 받기.
+        avatar: PNG, GIF 혹은 JPG. 최대 2MB. 400x400px로 다운스케일 될 것임
+        digest: 오랫동안 활동하지 않았을 때 받은 멘션들에 대한 요약 받기
         display_name:
           one: <span class="name-counter">1</span> 글자 남음
           other: <span class="name-counter">%{count}</span> 글자 남음
@@ -18,9 +18,9 @@ ko:
       imports:
         data: 다른 마스토돈 인스턴스에서 추출된 CSV 파일
       sessions:
-        otp: 2단계 인증 코드를 휴대전화를 보고 입력하거나, 복구 코드 중 하나를 사용
+        otp: 2단계 인증 코드를 휴대전화를 보고 입력하거나, 복구 코드 중 하나를 사용하세요.
       user:
-        filtered_languages: 선택된 언어가 공개 타임라인에서 제외 될 것입니다.
+        filtered_languages: 선택된 언어가 공개 타임라인에서 제외 될 것입니다
     labels:
       defaults:
         avatar: 아바타
diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml
index 17b9647a4..9876230b3 100644
--- a/config/locales/simple_form.nl.yml
+++ b/config/locales/simple_form.nl.yml
@@ -3,8 +3,8 @@ nl:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF of JPG. Maximaal 2MB. Wordt teruggeschaald naar 120x120px
-        digest: Wordt na een lange periode van inactiviteit verzonden met een samenvatting van vermeldingen tijdens je afwezigheid
+        avatar: PNG, GIF of JPG. Maximaal 2MB. Wordt teruggeschaald naar 400x400px
+        digest: Wordt alleen na een lange periode van inactiviteit verzonden en alleen wanneer je tijdens jouw afwezigheid persoonlijke berichten ontvangt
         display_name:
           one: <span class="name-counter">1</span> teken over
           other: <span class="name-counter">%{count}</span> tekens over
@@ -16,7 +16,7 @@ nl:
         setting_noindex: Heeft invloed op jouw openbare profiel en toots
         setting_theme: Heeft invloed op hoe de webapp van Mastodon eruitziet (op elk apparaat waarmee je inlogt).
       imports:
-        data: CSV-bestand dat op een andere Mastodon-server werd geëxporteerd
+        data: CSV-bestand dat op een andere Mastodonserver werd geëxporteerd
       sessions:
         otp: Voer de tweestaps-aanmeldcode vanaf jouw mobiele telefoon in of gebruik een van jouw herstelcode's.
       user:
@@ -45,6 +45,7 @@ nl:
         setting_default_privacy: Zichtbaarheid toots
         setting_default_sensitive: Media altijd als gevoelig markeren
         setting_delete_modal: Vraag voor het verwijderen van een toot een bevestiging
+        setting_display_sensitive_media: Als gevoelig gemarkeerde media altijd tonen
         setting_noindex: Jouw toots niet door zoekmachines laten indexeren
         setting_reduce_motion: Langzamere animaties
         setting_system_font_ui: Standaardlettertype van jouw systeem gebruiken
@@ -53,6 +54,7 @@ nl:
         severity: Zwaarte
         type: Importtype
         username: gebruikersnaam
+        username_or_email: Gebruikersnaam of e-mailadres
       interactions:
         must_be_follower: Meldingen van mensen die jou niet volgen blokkeren
         must_be_following: Meldingen van mensen die jij niet volgt blokkeren
diff --git a/config/locales/simple_form.no.yml b/config/locales/simple_form.no.yml
index 194e1582f..48710cabd 100644
--- a/config/locales/simple_form.no.yml
+++ b/config/locales/simple_form.no.yml
@@ -3,43 +3,63 @@
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF eller JPG. Maksimalt 2MB. Vil bli nedskalert til 120x120px
-        display_name: Maksimalt 30 tegn
+        avatar: PNG, GIF eller JPG. Maksimalt 2 MB. Vil bli nedskalert til 400x400px
+        digest: Kun sendt etter en lang periode med inaktivitet og bare dersom du har mottatt noen personlige meldinger mens du var borte
+        display_name:
+          one: <span class="name-counter">1</span> tegn igjen
+          other: <span class="name-counter">%{count}</span> tegn igjen
         header: PNG, GIF eller JPG. Maksimalt 2MB. Vil bli nedskalert til 700x335px
-        locked: Krever at du manuelt godkjenner følgere og setter standardbeskyttelse av poster til kun-følgere
-        note: Maksimalt 160 tegn
+        locked: Krever at du manuelt godkjenner følgere
+        note:
+          one: <span class="note-counter">1</span> tegn igjen
+          other: <span class="note-counter">%{count}</span> tegn igjen
+        setting_noindex: Påvirker din offentlige profil og statussider
+        setting_theme: Påvirker hvordan Mastodon ser ut når du er logget inn fra uansett enhet.
       imports:
-        data: CSV-fil eksportert fra en annen Mastodon instans
+        data: CSV-fil eksportert fra en annen Mastodon-instans
       sessions:
-        otp: Skriv tofaktorkoden fra din telefon eller bruk en av gjenopprettingskodene.
+        otp: Angi tofaktorkoden fra din telefon eller bruk en av dine gjenopprettingskoder.
+      user:
+        filtered_languages: Språk som er avhuket vil ikke vises på offentlige tidslinjer fra deg
     labels:
       defaults:
-        avatar: Profilbilde
+        avatar: Avatar
         confirm_new_password: Bekreft nytt passord
         confirm_password: Bekreft passord
         current_password: Nåværende passord
         data: Data
         display_name: Visningsnavn
         email: E-postadresse
+        expires_in: Utløper etter
+        filtered_languages: Filtrerte språk
         header: Overskrift
         locale: Språk
-        locked: Endre konto til privat
+        locked: Lås konto
+        max_uses: Maksimalt antall bruk
         new_password: Nytt passord
-        note: Biografi
+        note: Om meg
         otp_attempt: Tofaktorkode
         password: Passord
-        setting_auto_play_gif: Automatisk avspilling av GIFer
-        setting_boost_modal: Vis bekreftelsesdialog før fremheving
-        setting_default_privacy: Leserettigheter for poster
-        severity: Alvorlighet
+        setting_auto_play_gif: Autoavspill animert GIF-filer
+        setting_boost_modal: Vis bekreftelse før fremheving
+        setting_default_privacy: Postintegritet
+        setting_default_sensitive: Marker alltid media som sensitivt
+        setting_delete_modal: Vis bekreftelse før du sletter en tut
+        setting_noindex: Ikke delta i søkemotorsindeksering
+        setting_reduce_motion: Reduser bevegelser i animasjoner
+        setting_system_font_ui: Bruk systemets standardfont
+        setting_theme: Sidens tema
+        setting_unfollow_modal: Vis bekreftelse før du slutter å følge noen
+        severity: Alvorlighetsgrad
         type: Importeringstype
         username: Brukernavn
       interactions:
-        must_be_follower: Blokkér varslinger fra ikke-følgere
-        must_be_following: Blokkér varslinger fra brukere du ikke følger
+        must_be_follower: Blokker varslinger fra ikke-følgere
+        must_be_following: Blokker varslinger fra personer du ikke følger
+        must_be_following_dm: Blokker direkte meldinger fra personer du ikke følger
       notification_emails:
-        digest: Send oppsummeringseposter
-        favourite: Send e-post når noen liker din status
+        digest: Send sammendrag på e-post
+        favourite: Send e-post når noen setter din status som favoritt
         follow: Send e-post når noen følger deg
         follow_request: Send e-post når noen ber om å få følge deg
         mention: Send e-post når noen nevner deg
@@ -47,5 +67,5 @@
     'no': Nei
     required:
       mark: "*"
-      text: påkrevd
+      text: obligatorisk
     'yes': Ja
diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml
index 06c23ace2..addca75ce 100644
--- a/config/locales/simple_form.oc.yml
+++ b/config/locales/simple_form.oc.yml
@@ -3,8 +3,8 @@ oc:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhat en 120x120px
-        digest: Enviat aprèp un long moment d’inactivitat amb un resumit de las mencions qu’avètz recebudas pendent vòstra abséncia
+        avatar: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhat en 400x400px
+        digest: Solament enviat aprèp un long moment d’inactivitat e solament s’avètz recebut de messatges personals pendent vòstra abséncia
         display_name:
           one: Demòra encara <span class="name-counter">1</span> caractèr
           other: Demòran encara <span class="name-counter">%{count}</span> caractèrs
diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml
index dd987aab1..4f9594e6f 100644
--- a/config/locales/simple_form.pl.yml
+++ b/config/locales/simple_form.pl.yml
@@ -3,7 +3,7 @@ pl:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 120x120px
+        avatar: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 400x400px
         digest: Wysyłane tylko po długiej nieaktywności, jeżeli w tym czasie otrzymaleś jakąś wiadomość bezpośrednią
         display_name:
           few: Pozostały <span class="name-counter">%{count}</span> znaki.
@@ -49,6 +49,7 @@ pl:
         setting_default_privacy: Widoczność wpisów
         setting_default_sensitive: Zawsze oznaczaj zawartość multimedialną jako wrażliwą
         setting_delete_modal: Pytaj o potwierdzenie przed usunięciem wpisu
+        setting_display_sensitive_media: Zawsze oznaczaj zawartość multimedialną jako wrażliwą
         setting_favourite_modal: Pytaj o potwierdzenie przed dodaniem do ulubionych
         setting_noindex: Nie indeksuj mojego profilu w wyszukiwarkach internetowych
         setting_reduce_motion: Ogranicz ruch w animacjach
@@ -58,6 +59,7 @@ pl:
         severity: Priorytet
         type: Typ importu
         username: Nazwa użytkownika
+        username_or_email: Nazwa użytkownika lub adres e-mail
       interactions:
         must_be_follower: Nie wyświetlaj powiadomień od osób, które Cię nie śledzą
         must_be_following: Nie wyświetlaj powiadomień od osób, których nie śledzisz
diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml
index 2ed0e3329..0c22b2608 100644
--- a/config/locales/simple_form.pt-BR.yml
+++ b/config/locales/simple_form.pt-BR.yml
@@ -3,7 +3,7 @@ pt-BR:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF or JPG. Arquivos de até 2MB. Eles serão diminuídos para 120x120px
+        avatar: PNG, GIF or JPG. Arquivos de até 2MB. Eles serão diminuídos para 400x400px
         digest: Enviado após um longo período de inatividade com um resumo das menções que você recebeu em sua ausência
         display_name:
           one: <span class="name-counter">1</span> caracter restante
@@ -45,6 +45,7 @@ pt-BR:
         setting_default_privacy: Privacidade das postagens
         setting_default_sensitive: Sempre marcar mídia como sensível
         setting_delete_modal: Mostrar diálogo de confirmação antes de deletar uma postagem
+        setting_display_sensitive_media: Sempre mostrar mídia marcada como sensível
         setting_noindex: Não quero ser indexado por mecanismos de busca
         setting_reduce_motion: Reduz movimento em animações
         setting_system_font_ui: Usar a fonte padrão de seu sistema
@@ -53,6 +54,7 @@ pt-BR:
         severity: Gravidade
         type: Tipo de importação
         username: Nome de usuário
+        username_or_email: Nome de usuário ou e-mail
       interactions:
         must_be_follower: Bloquear notificações de não-seguidores
         must_be_following: Bloquear notificações de pessoas que você não segue
diff --git a/config/locales/simple_form.pt.yml b/config/locales/simple_form.pt.yml
index 9970247ab..67fed495c 100644
--- a/config/locales/simple_form.pt.yml
+++ b/config/locales/simple_form.pt.yml
@@ -3,50 +3,69 @@ pt:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF ou JPG. No máximo 2MB. Vai ser reduzido para 120x120px
-        display_name: No máximo 30 caracteres
-        header: PNG, GIF or JPG. No máximo 2MB. Vai ser reduzido para 700x335px
-        locked: Requer que manualmente aproves seguidores e torna o default dos teus posts para privados (apenas seguidores)
-        note: No máximo 160 caracteres
+        avatar: PNG, GIF or JPG. Arquivos até 2MB. Vão ser reduzidos para 400x400px
+        digest: Enviado após um longo período de inatividade e apenas se foste mencionado na tua ausência
+        display_name:
+          one: <span class="name-counter">1</span> caracter restante
+          other: <span class="name-counter">%{count}</span> caracteres restantes
+        header: PNG, GIF or JPG. Arquivos até 2MB. Vão ser reduzidos para 700x335px
+        locked: Requer aprovação manual de seguidores
+        note:
+          one: <span class="note-counter">1</span> caracter restante
+          other: <span class="note-counter">%{count}</span> caracteres restantes
+        setting_noindex: Afecta o teu perfil público e as páginas das tuas publicações
+        setting_theme: Afecta a aparência do Mastodon quando entras na tua conta em qualquer dispositivo.
       imports:
-        data: Ficheiro CSV exportado de outra instância do Mastodon
+        data: Arquivo CSV exportado de outra instância do Mastodon
       sessions:
-        otp: Insere o código de autenticação em dois passos do teu telefone ou utiliza um código de recuperação de acesso.
+        otp: Inserir o código de autenticação de dois factores do teu telemóvel ou usa um dos códigos de recuperação.
+      user:
+        filtered_languages: Seleciona os idiomas que devem ser removidos das tuas timelines públicas
     labels:
       defaults:
         avatar: Imagem de Perfil
-        confirm_new_password: Confirme nova palavra-passe
-        confirm_password: Confirme a palavra-passe
+        confirm_new_password: Confirmar nova palavra-passe
+        confirm_password: Confirmar palavra-passe
         current_password: Palavra-passe actual
-        data: Data
-        display_name: Nome
-        email: Endereço de email
+        data: Dados
+        display_name: Nome Público
+        email: Endereço de e-mail
+        expires_in: Expira em
+        filtered_languages: Idiomas filtrados
         header: Cabeçalho
-        locale: Língua
-        locked: Tornar conta privada
+        locale: Idioma
+        locked: Trancar conta
+        max_uses: Número máximo de utilizações
         new_password: Nova palavra-passe
         note: Biografia
         otp_attempt: Código de autenticação em dois passos
         password: Palavra-passe
-        setting_boost_modal: Pedir confirmação antes de partilhar um post
-        setting_default_privacy: Privacidade padrão de posts
-        setting_default_sensitive: Marcar sempre media como sensível
-        setting_reduce_motion: Reduzir movimento em animações
-        severity: Severity
-        type: Import type
-        username: Utilizador
+        setting_auto_play_gif: Reproduzir GIFs automaticamente
+        setting_boost_modal: Solicitar confirmação antes de partilhar uma publicação
+        setting_default_privacy: Privacidade da publicação
+        setting_default_sensitive: Sempre marcar media como sensível
+        setting_delete_modal: Solicitar confirmação antes de eliminar uma publicação
+        setting_noindex: Não quero ser indexado por motores de pesquisa
+        setting_reduce_motion: Reduz movimento em animações
+        setting_system_font_ui: Usar a fonte padrão do teu sistema
+        setting_theme: Tema do site
+        setting_unfollow_modal: Solicitar confirmação antes de deixar de seguir alguém
+        severity: Gravidade
+        type: Tipo de importação
+        username: Nome de utilizador
       interactions:
         must_be_follower: Bloquear notificações de não-seguidores
         must_be_following: Bloquear notificações de pessoas que não segues
+        must_be_following_dm: Bloquear mensagens directas de pessoas que tu não segues
       notification_emails:
-        digest: Enviar um email da actividade nesta instância
-        favourite: Enviar email quando alguém adiciona um post teu aos favoritos
-        follow: Enviar email quando alguém te segue
-        follow_request: Enviar um email quando alguém solicitar ser o teu seguidor
-        mention: Enviar email quando alguém te menciona
-        reblog: Enviar email quando alguém partilhar um post teu
+        digest: Enviar e-mails de resumo
+        favourite: Enviar e-mail quando alguém adiciona uma publicação tua aos favoritos
+        follow: Enviar e-mail quando alguém te segue
+        follow_request: Enviar e-mail quando alguém solicita ser teu seguidor
+        mention: Enviar e-mail quando alguém te menciona
+        reblog: Enviar e-mail quando alguém partilha uma publicação tua
     'no': Não
     required:
       mark: "*"
-      text: necessário
+      text: obrigatório
     'yes': Sim
diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml
index 1b780ac26..5c4df2189 100644
--- a/config/locales/simple_form.ru.yml
+++ b/config/locales/simple_form.ru.yml
@@ -3,7 +3,7 @@ ru:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF или JPG. Максимально 2MB. Будет уменьшено до 120x120px
+        avatar: PNG, GIF или JPG. Максимально 2MB. Будет уменьшено до 400x400px
         digest: Отсылается после долгого периода неактивности с общей информацией упоминаний, полученных в Ваше отсутствие
         display_name:
           few: Осталось <span class="name-counter">%{count}</span> символа
@@ -34,10 +34,12 @@ ru:
         data: Данные
         display_name: Показываемое имя
         email: Адрес e-mail
+        expires_in: Срок действия
         filtered_languages: Фильтруемые языки
         header: Заголовок
         locale: Язык
         locked: Сделать аккаунт приватным
+        max_uses: Макс. число использований
         new_password: Новый пароль
         note: О Вас
         otp_attempt: Двухфакторный код
@@ -49,8 +51,8 @@ ru:
         setting_delete_modal: Показывать диалог подтверждения перед удалением
         setting_noindex: Отказаться от индексации в поисковых машинах
         setting_reduce_motion: Уменьшить движение в анимации
-        setting_site_theme: Тема сайта
         setting_system_font_ui: Использовать шрифт системы по умолчанию
+        setting_theme: Тема сайта
         setting_unfollow_modal: Показывать диалог подтверждения перед тем, как отписаться от аккаунта
         severity: Строгость
         type: Тип импорта
@@ -58,6 +60,7 @@ ru:
       interactions:
         must_be_follower: Заблокировать уведомления не от подписчиков
         must_be_following: Заблокировать уведомления от людей, на которых Вы не подписаны
+        must_be_following_dm: Заблокировать личные сообщения от людей, на которых Вы не подписаны
       notification_emails:
         digest: Присылать дайджест по e-mail
         favourite: Уведомлять по e-mail, когда кому-то нравится Ваш статус
diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml
index 45e4c2954..dd3651ee3 100644
--- a/config/locales/simple_form.sk.yml
+++ b/config/locales/simple_form.sk.yml
@@ -3,17 +3,17 @@ sk:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 120x120px
-        digest: Sumár príspevkov ktoré ste dostali v dobe svojej neprítomnosti ak ste sa dlhší čas neprihlásili
+        avatar: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 400x400px
+        digest: Odoslané iba v prípade dlhodobej neprítomnosti, a len ak ste obdŕžali nejaké osobné správy kým ste boli preč
         display_name:
           one: Ostáva vám <span class="name-counter">1</span> znak
           other: Ostáva vám <span class="name-counter">%{count}</span> znakov
         header: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 700x335px
-        locked: Budete musieť manuálne schváliť sledujúcich
+        locked: Musíte manuálne schváliť sledujúcich
         note:
           one: Ostáva vám <span class="note-counter">1</span> znak
           other: Ostáva vám <span class="note-counter">%{count}</span>znakov
-        setting_noindex: Vaše príspevky a profil sa nebude dať nájsť vyhľadávaním
+        setting_noindex: Ovplyvňuje profil  a správy tak, že ich nebude možné nájsť vyhľadávaním
         setting_theme: Ovplyvní ako bude Mastodon vyzerať pri prihlásení z hociktorého zariadenia.
       imports:
         data: CSV súbor vyexportovaný z inej Mastodon inštancie
@@ -35,35 +35,37 @@ sk:
         header: Obrázok v hlavičke
         locale: Jazyk
         locked: Zamknúť účet
-        max_uses: Maximálny počet použití
+        max_uses: Maximálne možno použiť
         new_password: Nové heslo
-        note: O vás
-        otp_attempt: Dvoj-faktorový (2FA) kód
+        note: O tebe
+        otp_attempt: Dvoj-faktorový overovací (2FA) kód
         password: Heslo
         setting_auto_play_gif: Automaticky prehrávať animované GIFy
         setting_boost_modal: Zobrazovať potvrdzovacie okno pred re-toot
         setting_default_privacy: Nastavenie súkromia príspevkov
         setting_default_sensitive: Označiť každý obrázok/video/súbor ako chúlostivý
         setting_delete_modal: Zobrazovať potvrdzovacie okno pred zmazaním toot-u
+        setting_display_sensitive_media: Vždy zobrazovať médiá označované ako senzitívne
         setting_noindex: Nezaradzovať vaše príspevky do indexácie pre vyhľadávanie
         setting_reduce_motion: Redukovať pohyb v animáciách
-        setting_system_font_ui: Použiť štandardný systémový font
+        setting_system_font_ui: Použiť základné systémové písmo
         setting_theme: Vzhľad
         setting_unfollow_modal: Zobrazovať potvrdzovacie okno pred skončením sledovania iného používateľa
         severity: Závažnosť
         type: Typ importu
         username: Používateľské meno
+        username_or_email: Prezívka, alebo Email
       interactions:
-        must_be_follower: Blokovať notifikácie pod používateľov, ktorí vás nesledujú
-        must_be_following: Blokovať notifikácie od ľudí ktorý vás nesledujú
-        must_be_following_dm: Blokovať priame správy od ľudí ktorých nesleduješ
+        must_be_follower: Blokovať notifikácie pod používateľov, ktorí ťa nesledujú
+        must_be_following: Blokovať notifikácie od ľudí ktorí ťa nesledujú
+        must_be_following_dm: Blokovať súkromné správy od ľudí ktorých nesleduješ
       notification_emails:
         digest: Posielať súhrnné emaily
         favourite: Poslať email ak niekto označí váš príspevok ako obľúbený
-        follow: Poslať email ak vás niekto začne sledovať
-        follow_request: Poslať email ak vám niekto pošle žiadosť o sledovanie
-        mention: Poslať email ak vás niekto spomenie v svojom príspevku
-        reblog: Poslať email ak niekto re-tootne váš príspevok
+        follow: Poslať email, ak ťa niekto začne následovať
+        follow_request: Zaslať email ak ti niekto pošle žiadosť o sledovanie
+        mention: Poslať email ak ťa niekto spomenie v svojom príspevku
+        reblog: Poslať email ak niekto re-tootne tvoj príspevok
     'no': Nie
     required:
       mark: "*"
diff --git a/config/locales/simple_form.sr-Latn.yml b/config/locales/simple_form.sr-Latn.yml
index d811377c3..30f7eada7 100644
--- a/config/locales/simple_form.sr-Latn.yml
+++ b/config/locales/simple_form.sr-Latn.yml
@@ -3,7 +3,7 @@ sr-Latn:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF ili JPG. Najviše 2MB. Biće smanjena na 120x120px
+        avatar: PNG, GIF ili JPG. Najviše 2MB. Biće smanjena na 400x400px
         digest: Poslato posle dužeg perioda neaktivnosti sa pregledom svih bitnih stvari koje ste dobili dok ste bili odsutni
         display_name:
           few: <span class="name-counter">%{count}</span> karaktera preostala
diff --git a/config/locales/simple_form.sr.yml b/config/locales/simple_form.sr.yml
index 4e3599e5f..3345f7b41 100644
--- a/config/locales/simple_form.sr.yml
+++ b/config/locales/simple_form.sr.yml
@@ -3,7 +3,7 @@ sr:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF или JPG. Највише 2MB. Биће смањена на 120x120px
+        avatar: PNG, GIF или JPG. Највише 2MB. Биће смањена на 400x400px
         digest: Послато после дужег периода неактивности са прегледом свих битних ствари које сте добили док сте били одсутни
         display_name:
           few: <span class="name-counter">%{count}</span> карактера преостала
diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml
index ca5771085..8ef7bb064 100644
--- a/config/locales/simple_form.sv.yml
+++ b/config/locales/simple_form.sv.yml
@@ -3,13 +3,13 @@ sv:
   simple_form:
     hints:
       defaults:
-        avatar: Högst 2 MB. Kommer nedskalas till 120x120px
-        digest: Skickas efter en lång period av inaktivitet med en sammanfattning av anmärkningar du har fått under din frånvaro
+        avatar: Högst 2 MB. Kommer nedskalas till 400x400px
+        digest: Skickas endast efter en lång period av inaktivitet och endast om du har fått några personliga meddelanden i din frånvaro
         display_name:
           one: <span class="name-counter">1</span> tecken kvar
           other: <span class="name-counter">%{count}</span> tecken kvar
         header: NG, GIF eller JPG. Högst 2 MB. Kommer nedskalas till 700x335px
-        locked: Kräver dig att manuellt godkänna följare
+        locked: Kräver att du manuellt godkänner följare
         note:
           one: <span class="note-counter">1</span> tecken kvar
           other: <span class="note-counter">%{count}</span> tecken kvar
@@ -30,32 +30,37 @@ sv:
         data: Data
         display_name: Visningsnamn
         email: E-postadress
+        expires_in: Förfaller efter
         filtered_languages: Filtrerade språk
         header: Rubrik
         locale: Språk
         locked: Lås konto
+        max_uses: Högst antal  användningar
         new_password: Nytt lösenord
         note: Biografi
-        otp_attempt: Tvåfaktorkod
+        otp_attempt: Tvåstegslösenord
         password: Lösenord
         setting_auto_play_gif: Auto-play animerade GIF-filer
         setting_boost_modal: Visa bekräftelsedialog innan du knuffar
         setting_default_privacy: Postintegritet
         setting_default_sensitive: Markera alltid media som känsligt
-        setting_delete_modal: Visa bekräftelse innan du raderar en toot
+        setting_delete_modal: Visa bekräftelsedialog innan du raderar en toot
         setting_noindex: Uteslutning av sökmotorindexering
+        setting_reduce_motion: Minska rörelser i animationer
         setting_system_font_ui: Använd systemets standardfont
         setting_theme: Sidans tema
         setting_unfollow_modal: Visa bekräftelse innan du slutar följa någon
         severity: Strikthet
         type: Importtyp
         username: Användarnamn
+        username_or_email: Användarnamn eller e-mail
       interactions:
         must_be_follower: Blockera meddelanden från icke-följare
         must_be_following: Blockera meddelanden från personer du inte följer
+        must_be_following_dm: Blockera direktmeddelanden från personer du inte följer
       notification_emails:
         digest: Skicka sammandrag via e-post
-        favourite: Skicka e-post när någon favoriterar din status
+        favourite: Skicka e-post när någon favoriserar din status
         follow: Skicka e-post när någon följer dig
         follow_request: Skicka e-post när någon begär att följa dig
         mention: Skicka e-post när någon nämner dig
diff --git a/config/locales/simple_form.th.yml b/config/locales/simple_form.th.yml
index 87c673600..575b6a9f4 100644
--- a/config/locales/simple_form.th.yml
+++ b/config/locales/simple_form.th.yml
@@ -3,7 +3,7 @@ th:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF or JPG. At most 2MB. Will be downscaled to 120x120px
+        avatar: PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px
         display_name:
           one: <span class="name-counter">1</span> character left
           other: <span class="name-counter">%{count}</span> characters left
diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml
index 0e171b793..c827aac46 100644
--- a/config/locales/simple_form.tr.yml
+++ b/config/locales/simple_form.tr.yml
@@ -3,7 +3,7 @@ tr:
   simple_form:
     hints:
       defaults:
-        avatar: En fazla 2MB olacak şekilde PNG, GIF veya JPG formatında yükleyiniz. 120x120px büyüklüğüne indirgenecektir
+        avatar: En fazla 2MB olacak şekilde PNG, GIF veya JPG formatında yükleyiniz. 400x400px büyüklüğüne indirgenecektir
         display_name: <span class="name-counter">%{count}</span> karakter kaldı
         header: En fazla 2MB olacak şekilde PNG, GIF veya JPG formatında yükleyiniz. 700x335px büyüklüğüne indirgenecektir.
         locked: Takipçilerinizi manuel olarak kabul etmenizi ve gönderilerinizi varsayılan olarak sadece takipçilerinizin göreceği şekilde paylaşmanızı sağlar.
diff --git a/config/locales/simple_form.uk.yml b/config/locales/simple_form.uk.yml
index ff7f2d4ca..7bc3ee4b8 100644
--- a/config/locales/simple_form.uk.yml
+++ b/config/locales/simple_form.uk.yml
@@ -3,7 +3,7 @@ uk:
   simple_form:
     hints:
       defaults:
-        avatar: PNG, GIF, або JPG. Максимум - 2МБ. Буде зменшено до 120x120px
+        avatar: PNG, GIF, або JPG. Максимум - 2МБ. Буде зменшено до 400x400px
         display_name: 'Залишилося символів: <span class="name-counter">%{count}</span>'
         header: PNG, GIF, або JPG. Максимум - 2МБ. Буде зменшено до 700x335px
         locked: Буде вимагати від Вас самостійного підтверждення підписників, змінить приватність постів за замовчуванням на "тільки для підписників"
diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml
index 4eb993e52..825275756 100644
--- a/config/locales/simple_form.zh-CN.yml
+++ b/config/locales/simple_form.zh-CN.yml
@@ -3,7 +3,7 @@ zh-CN:
   simple_form:
     hints:
       defaults:
-        avatar: 文件大小限制 2MB,只支持 PNG、GIF 或 JPG 格式。图片分辨率将会压缩至 120×120px
+        avatar: 文件大小限制 2MB,只支持 PNG、GIF 或 JPG 格式。图片分辨率将会压缩至 400×400px
         digest: 仅在你长时间未登录,且收到了私信时发送
         display_name: 还能输入 <span class="name-counter">%{count}</span> 个字符
         header: 文件大小限制 2MB,只支持 PNG、GIF 或 JPG 格式。图片分辨率将会压缩至 700×335px
diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml
index 762629610..1e545da88 100644
--- a/config/locales/simple_form.zh-HK.yml
+++ b/config/locales/simple_form.zh-HK.yml
@@ -3,7 +3,7 @@ zh-HK:
   simple_form:
     hints:
       defaults:
-        avatar: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 120x120px
+        avatar: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 400x400px
         display_name: 最多 30 個字元
         header: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 700x335px
         locked: 你必須人手核准每個用戶對你的關注請求,而你的文章私隱會被預設為「只有關注你的人能看」
diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml
index b5edaea72..c7c5a37f0 100644
--- a/config/locales/simple_form.zh-TW.yml
+++ b/config/locales/simple_form.zh-TW.yml
@@ -3,7 +3,7 @@ zh-TW:
   simple_form:
     hints:
       defaults:
-        avatar: 支援 PNG, GIF 或 JPG 圖片,檔案大小上限為 2MB,會被縮小為 120x120px
+        avatar: 支援 PNG, GIF 或 JPG 圖片,檔案大小上限為 2MB,會被縮小為 400x400px
         display_name: 最多 30 個字元
         header: 支援 PNG, GIF 或 JPG 圖片,檔案大小上限為 2MB,會被縮小為 700x335px
         locked: 您必須手動核准每個使用者對您的關注請求,而您的文章隱私預設為「只有關注您的人能看」
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 4cf7d6f30..860431f1d 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -1,12 +1,12 @@
 ---
 sk:
   about:
-    about_hashtag_html: Toto sú verejné tooty otagované <strong>#%{tagom}</strong>. Ak máš účet niekde vo fediverse, môžeš ich používať.
+    about_hashtag_html: Toto sú verejné toot príspevky otagované <strong>#%{tagom}</strong>. Ak máš účet niekde vo fediverse, môžeš ich používať.
     about_mastodon_html: Mastodon je sociálna sieť založená na otvorených webových protokoloch. Jej zrojový kód je otvorený a je decentralizovaná podobne ako email.
-    about_this: Info
-    closed_registrations: Registrácie sú momentálne uzatvorené. Avšak, existujú ďalšie Mastodon inštancie kde si môžete založiť účet a získať prístup do tejto siete od nich.
+    about_this: O tejto instancii
+    closed_registrations: Registrácie sú momentálne uzatvorené. Avšak, môžeš nájsť nejaký iný Mastodon server kde si založ účet a získaj tak prístup do presne tej istej siete, odtiaľ.
     contact: Kontakt
-    contact_missing: Chýba
+    contact_missing: Nezadané
     contact_unavailable: Neuvedené
     description_headline: Čo je %{domain}?
     domain_count_after: ďalším inštanciám
@@ -23,7 +23,6 @@ sk:
       real_conversation_title: Vytvorený pre reálnu konverzáciu
       within_reach_body: Viacero aplikácií pre iOS, Android a iné platformy, ktoré vďaka jednoduchému API ekosystému vám dovoľujú byť online so svojimi priateľmi kdekoľvek.
       within_reach_title: Stále v dosahu
-    find_another_instance: Nájdi inú inštanciu
     generic_description: "%{domain} je jeden server v sieti"
     hosted_on: Mastodon hostovaný na %{domain}
     learn_more: Dozvedieť sa viac
@@ -44,12 +43,12 @@ sk:
     people_followed_by: Ľudia, ktorých %{name} sleduje
     people_who_follow: Ľudia sledujúci %{name}
     posts: Tooty
-    posts_with_replies: Tooty s odpoveďami
-    remote_follow: Vzdialené sledovanie
+    posts_with_replies: Toot príspevky s odpoveďami
+    remote_follow: Sleduj vzdialeného
     reserved_username: Prihlasovacie meno je rezervované
     roles:
-      admin: Admin
-      moderator: Mod
+      admin: Administrátor
+      moderator: Moderátor
     unfollow: Prestať sledovať
   admin:
     account_moderation_notes:
@@ -118,7 +117,7 @@ sk:
         moderator: Moderátor
         staff: Člen
         user: Používateľ
-      salmon_url: Salmon URL
+      salmon_url: Salmon adresa
       search: Hľadať
       shared_inbox_url: URL zdieľanej schránky
       show:
@@ -159,7 +158,7 @@ sk:
         unsuspend_account: "%{name} zrušil suspendáciu účtu používateľa %{target}"
         update_custom_emoji: "%{name} aktualizoval emoji %{target}"
         update_status: "%{name} aktualizoval status %{target}"
-      title: Audit log
+      title: Kontrólny záznam
     custom_emojis:
       by_domain: Doména
       copied_msg: Lokálna kópia emoji úspešne vytvorená
@@ -170,7 +169,7 @@ sk:
       destroyed_msg: Emojo úspešne zničený!
       disable: Zakázať
       disabled_msg: Emoji bolo úspešne zakázané
-      emoji: Emoji
+      emoji: Emotikony
       enable: Povoliť
       enabled_msg: Emoji bolo úspešne povolené
       image_hint: PNG do 50KB
@@ -194,7 +193,7 @@ sk:
         create: Blokovať doménu
         hint: Blokovanie domény stále dovolí vytvárať nové účty v databáze, ale tieto budú automaticky moderované.
         severity:
-          desc_html: "<strong>Stíšenie</strong> urobí všetky príspevky účtu neviditeľné pre všetkých ktorý nesledujú tento účet. <strong>Suspendácia</strong> zmaže všetky príspevky, médiá a profilové informácie. Použi <strong>Nič</strong> ak chceš iba neprijímať súbory médií."
+          desc_html: "<strong>Stíšenie</strong> urobí všetky príspevky účtu neviditeľné pre všetkých ktorý nenásledujú daný účet. <strong>Suspendácia</strong> zmaže všetky príspevky, médiá a profilové informácie. Použi <strong>Žiadne</strong>, ak chceš iba neprijímať súbory médií."
           noop: Nič
           silence: Stíšiť
           suspend: Suspendovať
@@ -202,7 +201,7 @@ sk:
       reject_media: Odmietať súbory s obrázkami alebo videami
       reject_media_hint: Zmaže lokálne uložené súbory médií a odmietne ich sťahovanie v budúcnosti. Irelevantné pre suspendáciu
       severities:
-        noop: Nič
+        noop: Žiadne
         silence: Stíšiť
         suspend: Suspendovať
       severity: Závažnosť
@@ -225,12 +224,428 @@ sk:
       domain: Doména
       new:
         create: Pridať doménu
+        title: Nový email na zablokovanie
+      title: Blokované emailové adresy
+    instances:
+      account_count: Známe účty
+      domain_name: Doména
+      reset: Resetovať
+      search: Hľadať
+      title: Známe instancie
+    invites:
+      filter:
+        all: Všetky
+        available: Dostupné
+        expired: Expirované
+        title: Filtrovať
+      title: Pozvánky
+    reports:
+      action_taken_by: Zákrok vykonal
+      are_you_sure: Ste si istý/á?
+      comment:
+        label: Vyjadriť sa
+        none: Žiadne
+      delete: Vymazať
+      id: Identifikácia
+      mark_as_resolved: Označiť ako vyriešené
+      nsfw:
+        'false': Odkryť mediálne prílohy
+        'true': Skryť mediálne prílohy
+      report: Nahlásiť
+      report_contents: Obsah
+      reported_account: Nahlásený účet
+      reported_by: Nahlásené užívateľom
+      resolved: Vyriešené
+      silence_account: Zamĺčať účet
+      status: Stav
+      suspend_account: Pozastaviť účet
+      target: Cieľ
+      title: Reporty
+      unresolved: Nevyriešené
+      view: Zobraziť
+    settings:
+      activity_api_enabled:
+        desc_html: Sčítanie lokálne publikovaných príspevkov, aktívnych užívateľov, a nových registrácii, v týždenných intervaloch
+        title: Vydať hromadné štatistiky o užívateľskej aktivite
+      bootstrap_timeline_accounts:
+        desc_html: Ak je prezývok viacero, každú oddeľte čiarkou. Možno zadať iba miestne, odomknuté účty. Pokiaľ necháte prázdne, je to pre všetkých miestnych administrátorov.
+        title: Štandardní následovníci nových užívateľov
+      contact_information:
+        email: Pracovný e-mail
+        username: Kontaktné užívateľské meno
+      hero:
+        desc_html: Zobrazuje sa na hlavnej stránke. Doporučuje sa rozlišenie aspoň 600x100px Pokiaľ tu nieje nič dodané, bude nastavený základný orázok tohoto serveru
+        title: Obrázok hrdinu
+      peers_api_enabled:
+        desc_html: Domény na ktoré táto instancia už vo fediverse natrafila
+        title: Zverejniť zoznam objavených instancií
+      registrations:
+        closed_message:
+          desc_html: Toto sa zobrazí na hlavnej stránke v prípade že sú registrácie uzavreté. Možno tu použiť aj HTML kód
+          title: Správa o uzavretých registráciách
+        deletion:
+          desc_html: Dovoľiť každému aby si mohli zmazať svok účet
+          title: Sprístupniť možnosť vymazať si účet
+        min_invite_role:
+          disabled: Nikto
+          title: Povoliť pozvánky od
+        open:
+          desc_html: Povoliť každému aby si mohli vytvoriť účet
+          title: Verejná registrácia
+      show_known_fediverse_at_about_page:
+        desc_html: Pokiaľ je zapnuté, bude v ukážke osi možné nahliadnúť toot statusy z celého známeho fediversa. V opačnom prípade tam budú ukázané iba statusy z lokálnej osi.
+        title: Ukázať celé známe fediversum ako ukážku osi
+      show_staff_badge:
+        desc_html: Zobraziť moderátorsku značku na užívateľovej stránke
+        title: Zobraziť značku moderátora
+      site_description:
+        desc_html: Oboznamujúci paragraf na hlavnej stránke a pri meta tagoch. Môžete použiť HTML kód, presnejšie  <code>&lt; a&gt;</code> a <code>&lt; em&gt; </code>.
+        title: Popis instancie
+      site_description_extended:
+        desc_html: Toto je vhodné miesto pre vaše pravidlá, oboznámenia a iné veci, ktorými je vaša instancia špecifická. Je možné tu používať HTML kód
+        title: Vlastné doplňujúce informácie
+      site_terms:
+        desc_html: Môžete si napísať vaše vlastné pravidla o súkromí, prevádzke, alebo aj iné legality. Môžete tu používať HTML kód
+        title: Vlastné pravidlá prevádzky
+      site_title: Názov instancie
+      thumbnail:
+        desc_html: Používané pre náhľady cez OpenGraph a API. Doporučuje sa rozlišenie 1200x630px
+        title: Miniatúra instancie
+      timeline_preview:
+        desc_html: Zobraziť verejnú nástenku na hlavnej stránke
+        title: Náhľad nástenky
+      title: Nastavenia stránky
+    statuses:
+      back_to_account: Späť na účet
+      batch:
+        delete: Vymazať
+        nsfw_off: Nevhodný obsah je vypnutý
+        nsfw_on: Nevhodný obsah je zapnutý
+      execute: Vykonať
+      failed_to_execute: Nepodarilo sa vykonať
+      media:
+        hide: Skryť médiá
+        show: Zobraziť médiá
+        title: Médiá
+      no_media: Žiadné médiá
+      title: Statusy na účte
+      with_media: S médiami
+    subscriptions:
+      callback_url: Zdrojová adresa URL
+      confirmed: Potvrdené
+      expires_in: Vyprší do
+      last_delivery: Posledné doručenie
+      title: WebSub
+      topic: Téma
+    title: Administrácia
+  admin_mailer:
+    new_report:
+      body: "%{reporter} nahlásil %{target}"
+      subject: Nový report pre %{instance} (#%{id})
+  application_mailer:
+    notification_preferences: Zmeniť e-mailové voľby
+    salutation: "%{name},"
+    settings: 'Zmeniť e-mailové voľby: %{link}'
+    view: 'Zobraziť:'
+    view_profile: Zobraziť profil
+    view_status: Zobraziť status
+  applications:
+    created: Aplikácia bola vytvorená úspešne
+    destroyed: Aplikáciu sa podarilo odstrániť
+    invalid_url: Zadaná URL adresa je nesprávna
+    regenerate_token: Znovu vygenerovať prístupový token
+    token_regenerated: Prístupový token bol úspešne vygenerovaný znova
+    warning: Na tieto údaje dávajte ohromný pozor. Nikdy ich s nikým nezďieľajte!
+    your_token: Váš prístupový token
   auth:
+    agreement_html: V rámci registrácie súhlasíte, že sa budete riadiť  <a href="%{rules_path}"> 1 pravidlami tejto instancie</a> 2 a taktiež <a href="%{terms_path}"> 3 našími servisnými podmienkami </a> 4.
+    change_password: Heslo
+    confirm_email: Potvrdiť email
+    delete_account: Vymazať účet
+    delete_account_html: Pokiaľ si želáte vymazať svoj účet, môžete tak <a href="%{path}"> 1 urobiť tu</a> 2. Budete požiadaný/á o potvrdenie tohto kroku.
+    didnt_get_confirmation: Neobdŕžali ste kroky pre potvrdenie?
+    forgot_password: Zabudli ste heslo?
+    invalid_reset_password_token: Token na obnovu hesla vypršal. Prosím vypítajte si nový.
     login: Prihlásenie
+    logout: Odhlásiť sa
+    migrate_account: Presunúť sa na iný účet
+    migrate_account_html: Pokiaľ si želáte presmerovať tento účet na nejaký iný, môžete <a href="%{path}"> tak urobiť tu</a>.
+    or: alebo
+    or_log_in_with: Alebo prihlásiť z
+    providers:
+      cas: CAS
+      saml: SAML
+    register: Zaregistrovať sa
+    register_elsewhere: Zaregistruj sa na inom serveri
+    resend_confirmation: Poslať potvrdzujúce pokyny znovu
+    reset_password: Resetovať heslo
+    security: Zabezpečenie
+    set_new_password: Nastaviť nové heslo
+  authorize_follow:
+    error: Naneštastie nastala chyba pri hľadaní vzdialeného účtu
+    follow: Následovať
+    follow_request: 'Poslali ste požiadavku následovať užívateľa:'
+    following: 'Podarilo sa! Teraz už následujete užívateľa:'
+    post_follow:
+      close: Alebo môžete iba zatvoriť toto okno.
+      return: Vrátiť sa na užívateľov profil
+      web: Ísť na web
+    title: Následovať %{acct}
+  datetime:
+    distance_in_words:
+      about_x_hours: "%{count}hod"
+      about_x_months: "%{count}mesiace"
+      about_x_years: "%{count}rok"
+      almost_x_years: "%{count}rok"
+      half_a_minute: Len teraz
+      less_than_x_seconds: Len teraz
+      over_x_years: "%{count}rok"
+      x_days: "%{count}dni"
+      x_minutes: ''
+      x_months: "%{count}mesiace"
+      x_seconds: "%{count}sek"
+  deletes:
+    bad_password_msg: Dobrý pokus, hakeri! Nesprávne heslo
+    confirm_password: Napíšte svoje terajšie heslo pre overenie vašej identity
+    description_html: Týmto <strong> natrvalo, nenavrátiteľne </strong> vymažeš obsah tvojho účtu, a deaktivuješ ho. Tvoja prezývka ale ostane rezervovaná ako prevencia pred budúcimi impersonáciami.
+    proceed: Vymazať účet
+    success_msg: Váš účet bol úspešne vymazaný
+    warning_html: Iba vymazanie obsahu z tejto konkrétnej instancie je garantované. Obsah ktorý bol zdieľaný široko-ďaleko pravdepodobne zanechá nejaké stopy. Servery ktoré sú offline a tie ktoré ignorujú tvoje zmeny teda nezaktualizujú svoje databázy.
+    warning_title: Dostupnosť distribuovaného obsahu
+  errors:
+    '403': Nemáte dostatočné povolenie na zobrazenie tejto stránky.
+    '404': Stránka ktorú ste hľadali neexistuje.
+    '410': Stránka ktorú tu hľadáte už viac neexistuje.
+    '422':
+      content: Bezpečtnostné overenie zlyhalo. Blokujete cookies?
+      title: Bezpečtnostné overenie zlyhalo
+    '429': Zamlčané
+    '500':
+      content: Ospravedlňujeme sa. Niečo sa pokazilo na našom konci.
+      title: Táto stránka nieje v poriadku
+    noscript_html: Aby bolo možné používať Mastodon web aplikáciu, prosím povoľte JavaScript. Alebo skúste jednu z <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md"> aplikácii </a> dostupných pre vašu platformu.
+  exports:
+    archive_takeout:
+      date: Dátum
+      download: Stiahni si svoj archív
+      hint_html: Môžeš si opýtať <strong>archív svojích príspevkov a nahratých médií</strong>. Exportované dáta budú v ActivityPub formáte, čítateľné hociakým kompatibilným softvérom.
+      in_progress: Balím tvoj archív...
+      request: Vyžiadaj si tvoj archív
+      size: Veľkosť
+    blocks: Blokujete
+    csv: CSV
+    follows: Následujete
+    mutes: Stíšili ste
+    storage: Úložisko médii
+  followers:
+    domain: Doména
+    explanation_html: Pokiaľ chceš zaručiť súkromie svojích príspevkov, musíš mať na vedomí, kto ťa sleduje. <strong> Tvoje súkromné príspevky sú doručené na každý server z ktorého ťa niekto následuje. </strong> Takže možno by si ich chcel/a skontrolovať, a odstrániť tých následovníkov, čo sú na serveroch ktorím nedôveruješ, že ich moderátori, alebo úpravbuy kódu budú tiež rešpektovať tvoje súkromie.
+    followers_count: Počet následovateľov
+    lock_link: Zamknite svoj účet
+    purge: Odstrániť následovateľa
+    success:
+      one: Počas utišovania sledovateľov z jednej domény...
+      other: Počas utišovania sledovateľov z %{count} domén...
+    true_privacy_html: Prosím majte na vedomí, <strong> 1 že ozajstné súkromie sa dá dosiahnúť iba za pomoci end-to-end enkrypcie</strong> 2.
+    unlocked_warning_html: Hocikto ťa môže následovať aby mohol/a ihneď vidieť tvoje súkromné príspevky. %{lock_link} aby si mohla skontrolovať a odmietať sledovateľov.
+    unlocked_warning_title: Tvoj účet nieje zamknutý
+  generic:
+    changes_saved_msg: Zmeny boli úspešne uložené!
+    powered_by: poháňané vďaka %{link}
+    save_changes: Uložiť zmeny
+    validation_errors:
+      one: Niečo nieje úplne v poriadku! Prosím skontroluj chybu
+      other: Niečo ešte stále nieje v poriadku! Prosím skontroluj všetky %{count} chyby
+  imports:
+    preface: Môžeš importovať dáta ktoré si exportoval/a z iného Mastodon serveru, ako sú napríklad zoznamy ľudí ktorých sleduješ, alebo blokuješ.
+    success: Tvoje dáta boli nahraté úspešne, a budú teraz spracované v danom čase
+    types:
+      blocking: Zoznam blokovaných
+      following: Zoznam sledovaných
+      muting: Zoznam ignorovaných
+    upload: Nahrať
+  in_memoriam_html: V pamäti.
+  invites:
+    delete: Deaktivovať
+    expired: Neplatné
+    expires_in:
+      '1800': 30 minút
+      '21600': 6 hodín
+      '3600': 1 hodina
+      '43200': 12 hodín
+      '86400': 1 deň
+    expires_in_prompt: Nikdy
+    generate: Vygeneruj
+    max_uses:
+      one: jedno použitie
+      other: "%{count} použití"
+    max_uses_prompt: Bez limitov
+    prompt: Vygeneruj a zdieľaj linky s ostatnými aby mali umožnený prístup k tomuto serveru
+    table:
+      expires_at: Vyprší
+      uses: Používa
+    title: Pozvať ľudí
+  landing_strip_html: "<strong>%{name}</strong> je užívateľ na serveri %{link_to_root_path}. Ty ich môžeš následovať a môžeš s nimi interaktovať pokiaľ máš účet hocikde v rámci fediversa."
+  landing_strip_signup_html: Pokiaľ ešte nemáš, môžeš <a href="%{sign_up_path}">si tu vytvoriť účet</a>.
+  lists:
+    errors:
+      limit: Dosiahli ste maximálny možný počet zoznamov
+  media_attachments:
+    validations:
+      images_and_video: K príspevku ktorý už obsahuje obrázky nemôžeš priložiť video
+      too_many: Nemôžeš priložiť viac ako 4 súbory
+  migrations:
+    acct: prezývka@doména nového účtu
+    currently_redirecting: 'Tvoj profil má nastavené presmerovanie na:'
+    proceed: Uložiť
+    updated_msg: Tvoje nastavenia pre presmerovanie účtu boli úspešne aktualizované!
+  moderation:
+    title: Moderovanie
+  notification_mailer:
+    digest:
+      action: Zobraziť všetky notifikácie
+      body: Tu nájdete krátky súhrn správ ktoré ste zmeškali od svojej poslednj návštevi od %{since}
+      mention: "%{name} ťa spomenul/a v:"
+      new_followers_summary:
+        one: Taktiež, získal/a si jedného nového následovníka zatiaľ čo si bol/a preč. Yay!
+        other: Taktiež, získal/a si %{count} nových následovníkov za tú dobu čo si bol/a preč. Yay!
+      subject:
+        one: "1 nová notifikácia od tvojej poslednej návštevy \U0001F418"
+        other: "%{count} nové notifikácie od tvojej poslednej návštevy \U0001F418"
+      title: Zatiaľ čo si bol/a preč…
+    favourite:
+      body: 'Tvoj príspevok bol uložený medi obľúbené užívateľa %{name}:'
+      subject: "%{name} si obľúbil/a tvoj príspevok"
+      title: Nové obľúbené
+    follow:
+      body: "%{name} ťa teraz následuje!"
+      subject: "%{name} ťa teraz následuje"
+      title: Nový sledovateľ
+    follow_request:
+      action: Spravuj žiadosti o sledovanie
+      body: "%{name} žiada povolenie ťa následovať"
+      subject: "%{name} ťa žiadá o možnosť sledovania"
+      title: Nová žiadosť o sledovanie
+    mention:
+      action: Odpovedať
+      body: "%{name} ťa spomenul/a v:"
+      subject: Boli ste spomenutí užívateľom %{name}
+      title: Nové spomenutie
+    reblog:
+      body: 'Tvoj príspevok bol pozdvihnutý užívateľom %{name}:'
+      subject: "%{name} pozdvihli tvoj príspevok"
+      title: Novo pozdvyhnuté
+  number:
+    human:
+      decimal_units:
+        format: "%n%u"
+        units:
+          billion: B
+          million: M
+          quadrillion: Q
+          thousand: K
+          trillion: T
+  pagination:
+    newer: Novšie
+    next: Ďalšie
+    older: Staršie
+    prev: Predošlé
+    truncate: "&hellip;"
+  preferences:
+    languages: Jazyky
+    other: Ostatné
+    publishing: Publikovanie
+    web: Web
+  push_notifications:
+    favourite:
+      title: "%{name} si obľúbil/a tvoj príspevok"
+    follow:
+      title: "%{name} ťa teraz následuje"
+    group:
+      title: "%{count} notifikácie"
+    mention:
+      action_boost: Pozdvihni
+      action_expand: Ukáž viac
+      action_favourite: Obľúbené
+      title: "%{name} ťa spomenul/a"
+    reblog:
+      title: "%{name} vyzdvihli tvoj príspevok"
+  remote_follow:
+    acct: Napíš svoju prezývku@doménu z ktorej chceš následovať
+    missing_resource: Nemôžeme nájsť potrebnú presmerovaciu adresu k tvojmu účtu
+    proceed: Začni následovať
+    prompt: 'Budeš sledovať:'
+  sessions:
+    activity: Najnovšia aktivita
+    browser: Prehliadač
+    browsers:
+      alipay: Alipay
+      generic: Neznámy prehliadač
+    current_session: Aktuálna sezóna
+    description: "%{browser} na %{platform}"
+    explanation: Tieto sú prehliadače ktoré sú teraz prihlásené na tvoj Mastodon účet.
+    ip: IP adresa
+    platforms:
+      mac: MacOSX
+      other: neznáma platforma
+    revoke: Zamietni
+    revoke_success: Sezóna úspešne zamietnutá
+    title: Sezóna
   settings:
     authorized_apps: Autorizované aplikácie
     back: Naspäť na stránku
+    delete: Zmazanie účtu
+    development: Vývoj
+    edit_profile: Upraviť profil
+    export: Exportovať dáta
+    followers: Povolení sledovatelia
+    import: Importovať
+    migrate: Presunúť účet
+    notifications: Oznámenia
+    preferences: Možnosti
+    settings: Nastavenia
+    two_factor_authentication: Dvoj-faktorové overenie
+    your_apps: Tvoje aplikácie
+  statuses:
+    open_in_web: Otvor v okne prehliadača
+    over_character_limit: limit počtu %{max} znakov bol presiahnutý
+    pin_errors:
+      limit: Už ste si pripli ten najvyšší možný počet príspevkov
+      ownership: Nemožno pripnúť príspevok od niekoho iného
+      private: Neverejné príspevky nemôžu byť pripnuté
+    show_more: Ukáž viac
+    visibilities:
+      private: Iba pre sledovateľov
+      private_long: Ukáž iba následovateľom
+      public: Verejné
+      public_long: Všetci môžu vidieť
+      unlisted: Nezaradené
+      unlisted_long: Všetci môžu vidieť, ale nieje zaradené do verejnej osi
+  stream_entries:
+    click_to_show: Klikni pre zobrazenie
+    pinned: Pripnutý toot
+    reblogged: vyzdvihnutý
+    sensitive_content: Senzitívny obsah
+  terms:
+    title: Podmienky užívania, a pravidlá o súkromí pre %{instance}
+  two_factor_authentication:
+    setup: Nastavenie
+  user_mailer:
+    backup_ready:
+      explanation: Vyžiadal/a si si úplnú zálohu tvojho Mastodon účtu. Táto záloha je teraz pripravená na stiahnutie!
+      subject: Tvoj archív je pripravený na stiahnutie
+      title: Odber archívu
+    welcome:
+      edit_profile_action: Nastav profil
+      explanation: Tu nájdeš nejaké tipy do začiatku
+      final_action: Začni prispievať
+      final_step: 'Začnite písať! Aj bez následovníkov budú vaše verejné správy videné ostatnými, napríklad na lokálnej osi a pod haštagmi. Môžete sa ostatným predstaviť pod haštagom #introductions.'
+      review_preferences_action: Zmeniť nastavenia
+      subject: Vitaj na Mastodone
+      tip_local_timeline: Lokálna os je celkový pohľad na aktivitu užívateľov %{instance}. Toto sú tvoji najbližší susedia!
+      tip_mobile_webapp: Pokiaľ ti prehliadač ponúkne možnosť pridať Mastodon na tvoju obrazovku, môžeš potom dostávať notifikácie skoro ako z natívnej aplikácie!
+      title: Vitaj na palube, %{name}!
   users:
     invalid_email: Emailová adresa je neplatná
-    invalid_otp_token: Neplatný 2FA kód
+    invalid_otp_token: Neplatný kód pre dvojfaktorovú autentikáciu
     signed_in_as: 'Prihlásený ako:'
diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml
index ac80e81ec..2d984049a 100644
--- a/config/locales/sr-Latn.yml
+++ b/config/locales/sr-Latn.yml
@@ -23,7 +23,6 @@ sr-Latn:
       real_conversation_title: Pravljen za pravi razgovor
       within_reach_body: Više aplikacija za iOS, Android, kao i druge platforme zahvaljujući ekosistemu dobrih API-ja će Vam omogućiti da ostanete u kontaktu sa prijateljima svuda.
       within_reach_title: Uvek u kontaktu
-    find_another_instance: Nađite drugu instancu
     generic_description: "%{domain} je server na mreži"
     hosted_on: Mastodont hostovan na %{domain}
     learn_more: Saznajte više
@@ -347,7 +346,6 @@ sr-Latn:
     your_token: Vaš pristupni token
   auth:
     agreement_html: Pristupanjem instanci se slažete sa <a href="%{rules_path}">pravilima instance</a> i <a href="%{terms_path}">uslovima korišćenja</a>.
-    change_password: Bezbednost
     delete_account: Obriši nalog
     delete_account_html: Ako želite da obrišete Vaš nalog, možete <a href="%{path}">nastaviti ovde</a>. Bićete upitani da potvrdite.
     didnt_get_confirmation: Niste dobili poruku sa uputstvima za potvrdu naloga?
@@ -360,6 +358,7 @@ sr-Latn:
     register: Registruj se
     resend_confirmation: Pošalji poruku sa uputstvima o potvrdi naloga ponovo
     reset_password: Resetuj lozinku
+    security: Bezbednost
     set_new_password: Postavi novu lozinku
   authorize_follow:
     error: Nažalost, desila se greška pri traženju udaljenog naloga
diff --git a/config/locales/sr.yml b/config/locales/sr.yml
index 755396828..2daf32915 100644
--- a/config/locales/sr.yml
+++ b/config/locales/sr.yml
@@ -23,7 +23,6 @@ sr:
       real_conversation_title: Прављен за прави разговор
       within_reach_body: Више апликација за iOS, Андроид, као и друге платформе захваљујући екосистему добрих API-ја ће Вам омогућити да останете у контакту са пријатељима свуда.
       within_reach_title: Увек у контакту
-    find_another_instance: Нађите другу инстанцу
     generic_description: "%{domain} је сервер на мрежи"
     hosted_on: Мастодонт хостован на %{domain}
     learn_more: Сазнајте више
@@ -347,7 +346,6 @@ sr:
     your_token: Ваш приступни токен
   auth:
     agreement_html: Приступањем инстанци се слажете са <a href="%{rules_path}">правилима инстанце</a> и <a href="%{terms_path}">условима коришћења</a>.
-    change_password: Безбедност
     delete_account: Обриши налог
     delete_account_html: Ако желите да обришете Ваш налог, можете <a href="%{path}">наставити овде</a>. Бићете упитани да потврдите.
     didnt_get_confirmation: Нисте добили поруку са упутствима за потврду налога?
@@ -360,6 +358,7 @@ sr:
     register: Региструј се
     resend_confirmation: Пошаљи поруку са упутствима о потврди налога поново
     reset_password: Ресетуј лозинку
+    security: Безбедност
     set_new_password: Постави нову лозинку
   authorize_follow:
     error: Нажалост, десила се грешка при тражењу удаљеног налога
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index b6595cb0d..8d9c6d5df 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -2,7 +2,7 @@
 sv:
   about:
     about_hashtag_html: Dessa är offentliga toots märkta med <strong>#%{hashtag}</strong>. Du kan interagera med dem om du har ett konto någonstans i federationen.
-    about_mastodon_html: Mastodon är ett socialt nätverk baserat på öppna webbprotokoll och gratis, öppen källkodsprogramvara. Det är decentraliserat som e-post
+    about_mastodon_html: Mastodon är ett socialt nätverk baserat på öppna webbprotokoll och gratis, öppen källkodsprogramvara. Det är decentraliserat som e-post.
     about_this: Om
     closed_registrations: Registreringar är för närvarande stängda i denna instans. Dock så kan du hitta en annan instans för att skapa ett konto och få tillgång till samma nätverk från det.
     contact: Kontakt
@@ -23,7 +23,6 @@ sv:
       real_conversation_title: Byggd för riktiga konversationer
       within_reach_body: Flera appar för iOS, Android och andra plattformar tack vare ett utvecklingsvänligt API-ekosystem gör att du kan hålla kontakten med dina vänner var som helst.
       within_reach_title: Alltid inom räckhåll
-    find_another_instance: Hitta en annan instans
     generic_description: "%{domain} är en server i nätverket"
     hosted_on: Mastodon värd på %{domain}
     learn_more: Lär dig mer
@@ -39,6 +38,7 @@ sv:
     followers: Följare
     following: Följer
     media: Media
+    moved_html: "%{name} har flyttat till %{new_profile_link}:"
     nothing_here: Det finns inget här!
     people_followed_by: Personer som %{name} följer
     people_who_follow: Personer som följer %{name}
@@ -48,6 +48,7 @@ sv:
     reserved_username: Användarnamnet är reserverat
     roles:
       admin: Admin
+      moderator: Moderera
     unfollow: Sluta följa
   admin:
     account_moderation_notes:
@@ -59,25 +60,33 @@ sv:
       destroyed_msg: Modereringsnotering borttagen utan problem!
     accounts:
       are_you_sure: Är du säker?
+      by_domain: Domän
       confirm: Bekräfta
       confirmed: Bekräftad
+      demote: Degradera
+      disable: inaktivera
       disable_two_factor_authentication: Inaktivera 2FA
+      disabled: inaktiverad
       display_name: Visningsnamn
       domain: Domän
       edit: Redigera
       email: E-post
-      feed_url: Feed URL
+      enable: Aktivera
+      enabled: Aktiverad
+      feed_url: Flödes URL
       followers: Följare
       followers_url: Följare URL
       follows: Följs
-      inbox_url: Inbox URL
+      inbox_url: Inkorgs URL
       ip: IP
       location:
         all: Alla
         local: Lokal
         remote: Avlägsen
         title: Plats
+      login_status: Inloggningsstatus
       media_attachments: Media bifogade filer
+      memorialize: Förvandla till ett memoriam
       moderation:
         all: Alla
         silenced: Tystas
@@ -94,6 +103,7 @@ sv:
       outbox_url: Utkorg URL
       perform_full_suspension: Utför full avstängning
       profile_url: Profil URL
+      promote: Befordra
       protocol: Protokoll
       public: Offentlig
       push_subscription_expires: PuSH-prenumerationen löper ut
@@ -101,28 +111,61 @@ sv:
       reset: Återställ
       reset_password: Återställ lösenord
       resubscribe: Starta en ny prenumeration
+      role: Behörigheter
+      roles:
+        admin: Administratör
+        moderator: Moderator
+        staff: Personal
+        user: Användare
       salmon_url: Lax URL
       search: Sök
       shared_inbox_url: Delad inkorg URL
       show:
-        created_reports: Rapporter som skapats av det här kontot
-        report: rapport
-        targeted_reports: Rapporter gjorda om detta konto
+        created_reports: Anmälningar som skapats av det här kontot
+        report: anmäla
+        targeted_reports: Anmälningar gjorda om detta konto
       silence: Tystnad
       statuses: Status
       subscribe: Prenumerera
       title: Konton
       undo_silenced: Ångra tystnad
       undo_suspension: Ångra avstängning
-      unsubscribe: Säga upp
+      unsubscribe: Avsluta prenumeration
       username: Användarnamn
       web: Webb
+    action_logs:
+      actions:
+        confirm_user: "%{name} bekräftade e-postadress för användare %{target}"
+        create_custom_emoji: "%{name} laddade upp ny emoji %{target}"
+        create_domain_block: "%{name} blockerade domän %{target}"
+        create_email_domain_block: "%{name} svartlistade e-postdomän %{target}"
+        demote_user: "%{name} degraderade användare %{target}"
+        destroy_domain_block: "%{name} avblockerade domän %{target}"
+        destroy_email_domain_block: "%{name} vitlistade e-postdomän %{target}"
+        destroy_status: "%{name} tog bort status av %{target}"
+        disable_2fa_user: "%{name} inaktiverade tvåstegsautentiseringskrav för användare %{target}"
+        disable_custom_emoji: "%{name} inaktiverade emoji %{target}"
+        disable_user: "%{name} inaktiverade inloggning för användare %{target}"
+        enable_custom_emoji: "%{name} aktiverade emoji %{target}"
+        enable_user: "%{name} aktiverade inloggning för användare %{target}"
+        memorialize_account: "%{name} omvandlade %{target}s konto till en memoriam-sida"
+        promote_user: "%{name} flyttade upp användare %{target}"
+        reset_password_user: "%{name} återställde lösenord för användaren %{target}"
+        resolve_report: "%{name} avvisade anmälan %{target}"
+        silence_account: "%{name} tystade ner %{target}s konto"
+        suspend_account: "%{name} suspenderade %{target}s konto"
+        unsilence_account: "%{name} återljudade %{target}s konto"
+        unsuspend_account: "%{name} aktiverade %{target}s konto"
+        update_custom_emoji: "%{name} uppdaterade emoji %{target}"
+        update_status: "%{name} uppdaterade status för %{target}"
+      title: Revisionslogg
     custom_emojis:
+      by_domain: Domän
       copied_msg: Skapade en lokal kopia av emoji utan problem
       copy: Kopia
       copy_failed_msg: Kunde inte skapa en lokal kopia av den emoji
       created_msg: Emoji skapades utan problem!
-      delete: Ta bort
+      delete: Radera
       destroyed_msg: Emojo borttagen utan problem!
       disable: Inaktivera
       disabled_msg: Inaktiverade emoji utan problem
@@ -130,53 +173,58 @@ sv:
       enable: Aktivera
       enabled_msg: Aktiverade den emoji utan problem
       image_hint: PNG upp till 50KB
+      listed: Noterade
       new:
         title: Lägg till ny egen emoji
+      overwrite: Skriva över
       shortcode: Kortkod
       shortcode_hint: Minst 2 tecken, endast alfanumeriska tecken och understreck
-      title: Custom emojis
+      title: Egentillverkade emojis
+      unlisted: Olistade
+      update_failed_msg: Kunde inte uppdatera emoji
+      updated_msg: Emoji uppdaterades utan problem!
       upload: Ladda upp
     domain_blocks:
       add_new: Lägg till ny
       created_msg: Domänblocket behandlas nu
-      destroyed_msg: Domänblocket är ogjord
+      destroyed_msg: Domänblockering har återtagits
       domain: Domän
       new:
         create: Skapa block
         hint: Domänblocket hindrar inte skapandet av kontoposter i databasen, men kommer retroaktivt, automatiskt att tillämpa specifika modereringsmetoder på dessa konton.
         severity:
-          desc_html: "<strong>Tystnad</strong> kommer att göra kontoinlägg osynliga för alla som inte följer dem. <strong>Suspendera</strong> tar bort allt kontons innehåll, media och profildata. Använd <strong>Ingen</strong> om du bara vill avvisa mediefiler."
+          desc_html: "<strong>Tysta ner</strong> kommer att göra kontoinlägg osynliga för alla som inte följer dem. <strong>Suspendera</strong> kommer ta bort allt av kontots innehåll, media och profildata. Använd <strong>Ingen</strong> om du bara vill avvisa mediefiler."
           noop: Ingen
-          silence: Tystnad
+          silence: Tysta ner
           suspend: Suspendera
         title: Nytt domänblock
       reject_media: Avvisa mediafiler
-      reject_media_hint: Ta bort lokalt lagrade mediefiler och tar bort möjligheten att ladda ner något i framtiden. Irrelevant för suspensioner
+      reject_media_hint: Raderar lokalt lagrade mediefiler och förhindrar möjligheten att ladda ner något i framtiden. Irrelevant för suspensioner
       severities:
         noop: Ingen
-        silence: Tystnad
+        silence: Tysta ner
         suspend: Suspendera
       severity: Svårighet
       show:
         affected_accounts:
           one: Ett konto i databasen drabbades
-          other: "%{count} konton i databasen drabbades"
+          other: "%{count} konton i databasen har drabbats"
         retroactive:
-          silence: Ta bort tystnad från alla befintliga konton från den här domänen
-          suspend: Ta bort suspendering från alla befintliga konton i den här domänen
+          silence: Ta bort tysta ner från alla befintliga konton på den här domänen
+          suspend: Ta bort suspendering från alla befintliga konton på den här domänen
         title: Ångra domänblockering för %{domain}
         undo: Ångra
       title: Domänblockering
       undo: Ångra
     email_domain_blocks:
       add_new: Lägg till ny
-      created_msg: E-postdomänblocket har skapats
-      delete: Ta bort
-      destroyed_msg: E-postdomänblocket har tagits bort
+      created_msg: E-postdomän har lagts till i domänblockslistan utan problem
+      delete: Radera
+      destroyed_msg: E-postdomän har tagits bort från domänblockslistan utan problem
       domain: Domän
       new:
-        create: Skapa block
-        title: Nytt E-postdomänblock
+        create: Skapa domän
+        title: Ny E-postdomänblocklistningsinmatning
       title: E-postdomänblock
     instances:
       account_count: Kända konton
@@ -184,56 +232,78 @@ sv:
       reset: Återställa
       search: Sök
       title: Kända instanser
+    invites:
+      filter:
+        all: Alla
+        available: Tillgängliga
+        expired: Utgångna
+        title: Filtrera
+      title: Inbjudningar
     reports:
       action_taken_by: Åtgärder vidtagna av
       are_you_sure: Är du säker?
       comment:
         label: Kommentar
         none: Ingen
-      delete: Ta bort
+      delete: Radera
       id: ID
       mark_as_resolved: Markera som löst
       nsfw:
         'false': Visa bifogade mediafiler
         'true': Dölj bifogade mediafiler
-      report: 'Rapportera #%{id}'
+      report: 'Anmäl #%{id}'
       report_contents: Innehåll
-      reported_account: Rapporterat konto
-      reported_by: Rapporterad av
+      reported_account: Anmält konto
+      reported_by: Anmäld av
       resolved: Löst
-      silence_account: Tystat konto
+      silence_account: Tysta ner konto
       status: Status
-      suspend_account: Suspenderat konto
+      suspend_account: Suspendera konto
       target: Mål
-      title: Rapporter
+      title: Anmälningar
       unresolved: Olösta
       view: Granska
     settings:
+      activity_api_enabled:
+        desc_html: Räkning av lokalt postade statusar, aktiva användare och nyregistreringar per vecka
+        title: Publicera uppsamlad statistik om användaraktivitet
       bootstrap_timeline_accounts:
         desc_html: Separera flera användarnamn med kommatecken. Endast lokala och olåsta konton kommer att fungera. Standard när det är tomt och alla är lokala administratörer.
         title: Standard att följa för nya användare
       contact_information:
         email: Företag E-post
-        username: Kontakten användarnamn
+        username: Användarnamn för kontakt
+      peers_api_enabled:
+        desc_html: Domännamn denna instans har påträffat i fediverse
+        title: Publicera lista över upptäckta instanser
       registrations:
         closed_message:
           desc_html: Visas på framsidan när registreringen är stängd. Du kan använda HTML-taggar
           title: Stängt registreringsmeddelande
         deletion:
           desc_html: Tillåt alla att ta bort sitt konto
-          title: Open account deletion
+          title: Öppen kontoradering
+        min_invite_role:
+          disabled: Ingen
+          title: Tillåt inbjudningar av
         open:
           desc_html: Tillåt alla att skapa ett konto
           title: Öppen registrering
+      show_known_fediverse_at_about_page:
+        desc_html: När den växlas, kommer toots från hela fediverse visas på förhandsvisning. Annars visas bara lokala toots.
+        title: Visa det kända fediverse på tidslinjens förhandsgranskning
+      show_staff_badge:
+        desc_html: Visa en personalbricka på en användarsida
+        title: Visa personalbricka
       site_description:
         desc_html: Inledande stycke på framsidan och i metataggar. Du kan använda HTML-taggar, i synnerhet <code>&lt;a&gt;</code> och <code>&lt;em&gt;</code>.
         title: Instansbeskrivning
       site_description_extended:
         desc_html: Ett bra ställe för din uppförandekod, regler, riktlinjer och andra saker som stämmer med din instans. Du kan använda HTML-taggar
-        title: Custom utökad information
+        title: Egentillverkad utökad information
       site_terms:
         desc_html: Du kan skriva din egen integritetspolicy, användarvillkor eller andra regler. Du kan använda HTML-taggar
-        title: Custom Villkor för tjänster
+        title: Egentillverkad villkor för tjänster
       site_title: Namn på instans
       thumbnail:
         desc_html: Används för förhandsgranskningar via OpenGraph och API. 1200x630px rekommenderas
@@ -245,11 +315,11 @@ sv:
     statuses:
       back_to_account: Tillbaka till kontosidan
       batch:
-        delete: Ta bort
+        delete: Radera
         nsfw_off: NSFW AV
         nsfw_on: NSFW PÅ
-      execute: Kör
-      failed_to_execute: Misslyckades att köra
+      execute: Utför
+      failed_to_execute: Misslyckades att utföra
       media:
         hide: Dölj media
         show: Visa media
@@ -260,7 +330,7 @@ sv:
     subscriptions:
       callback_url: Återanrop URL
       confirmed: Bekräftad
-      expires_in: Går ut om
+      expires_in: Utgår om
       last_delivery: Sista leverans
       title: WebSub
       topic: Ämne
@@ -270,9 +340,12 @@ sv:
       body: "%{reporter} har rapporterat %{target}"
       subject: Ny rapport för %{instance} (#%{id})
   application_mailer:
+    notification_preferences: Ändra e-postinställningar
     salutation: "%{name},"
-    settings: 'Change e-mail preferences: %{link}'
+    settings: 'Ändra e-postinställningar: %{link}'
     view: 'Granska:'
+    view_profile: Visa profil
+    view_status: Visa status
   applications:
     created: Ansökan är framgångsrikt skapad
     destroyed: Ansökan är framgångsrikt borttagen
@@ -282,8 +355,8 @@ sv:
     warning: Var mycket försiktig med denna data. Dela aldrig den med någon!
     your_token: Din access token
   auth:
-    agreement_html: Genom att registrera dig godkänner du <a href="%{rules_path}">våra användarvillkor</a> och <a href="%{terms_path}">sekretesspolicy</a>.
-    change_password: Säkerhet
+    agreement_html: Genom att registrera dig godkänner du att följa <a href="%{rules_path}">instansens regler</a> och <a href="%{terms_path}">våra användarvillkor</a>.
+    confirm_email: Bekräfta e-postadress
     delete_account: Ta bort konto
     delete_account_html: Om du vill radera ditt konto kan du <a href="%{path}">fortsätta här</a>. Du kommer att bli ombedd att bekräfta.
     didnt_get_confirmation: Fick inte instruktioner om bekräftelse?
@@ -291,9 +364,16 @@ sv:
     invalid_reset_password_token: Lösenordsåterställningstoken är ogiltig eller utgått. Vänligen be om en ny.
     login: Logga in
     logout: Logga ut
+    migrate_account: Flytta till ett annat konto
+    migrate_account_html: Om du vill omdirigera detta konto till ett annat, kan du <a href="%{path}">konfigurera det här</a>.
+    or_log_in_with: Eller logga in med
+    providers:
+      cas: CAS
+      saml: SAML
     register: Registrera
     resend_confirmation: Skicka instruktionerna om bekräftelse igen
     reset_password: Återställ lösenord
+    security: Säkerhet
     set_new_password: Skriv in nytt lösenord
   authorize_follow:
     error: Tyvärr inträffade ett fel när vi kontrollerade fjärrkontot
@@ -359,7 +439,7 @@ sv:
     unlocked_warning_title: Ditt konto är inte låst
   generic:
     changes_saved_msg: Ändringar sparades framgångsrikt!
-    powered_by: powered by %{link}
+    powered_by: drivs av %{link}
     save_changes: Spara ändringar
     validation_errors:
       one: Något är inte riktigt rätt ännu! Kontrollera felet nedan
@@ -372,37 +452,77 @@ sv:
       following: Följare lista
       muting: Tystade lista
     upload: Ladda upp
+  in_memoriam_html: Till minne av.
+  invites:
+    delete: Avaktivera
+    expired: Utgånget
+    expires_in:
+      '1800': 30 minuter
+      '21600': 6 timmar
+      '3600': 1 timma
+      '43200': 12 timmar
+      '86400': 1 dag
+    expires_in_prompt: Aldrig
+    generate: Skapa
+    max_uses:
+      one: 1 användning
+      other: "%{count} användningar"
+    max_uses_prompt: Ingen gräns
+    prompt: Skapa och dela länkar med andra för att ge tillgång till denna instans
+    table:
+      expires_at: Utgår
+      uses: Användningar
+    title: Bjud in andra
   landing_strip_html: "<strong>%{name}</strong> är en användare på %{link_to_root_path}. Du kan följa dem eller interagera med dem om du har ett konto någonstans i federationen."
   landing_strip_signup_html: Om du inte gör det, så kan du <a href="%{sign_up_path}">registrera dig här</a>.
+  lists:
+    errors:
+      limit: Du har nått det maximala antalet listor
   media_attachments:
     validations:
       images_and_video: Det går inte att bifoga en video till en status som redan innehåller bilder
       too_many: Det går inte att bifoga mer än 4 filer
+  migrations:
+    acct: användarnamn@domän av det nya kontot
+    currently_redirecting: 'Din profil är satt att omdirigeras till:'
+    proceed: Spara
+    updated_msg: Dina kontoflyttsinställning har uppdaterats!
+  moderation:
+    title: Moderera
   notification_mailer:
     digest:
-      body: 'Här är en kort sammanfattning av vad du missat på %{instance} sedan ditt senaste besök på %{since}:'
+      action: Visa alla notifieringar
+      body: Här är en kort sammanfattning av de meddelanden du missade sedan ditt senaste besök på %{since}
       mention: "%{name} nämnde dig i:"
       new_followers_summary:
-        one: Du har förvärvat en ny följare! Jippie!
-        other: Du har fått %{count} nya följare! Otroligt!
+        one: Du har också förvärvat en ny följare! Jippie!
+        other: Du har också fått %{count} nya följare medans du var iväg! Otroligt!
       subject:
         one: "1 nytt meddelande sedan ditt senaste besök \U0001F418"
         other: "%{count} nya meddelanden sedan ditt senaste besök \U0001F418"
+      title: I din frånvaro…
     favourite:
       body: 'Din status favoriserades av %{name}:'
       subject: "%{name} favoriserade din status"
+      title: Ny favorit
     follow:
       body: "%{name} följer nu dig!"
       subject: "%{name} följer nu dig"
+      title: Ny följare
     follow_request:
+      action: Hantera följefterfrågningar
       body: "%{name} har begärt att följa dig"
-      subject: 'Pending follower: %{name}'
+      subject: 'Väntande följare: %{name}'
+      title: Ny följefterfrågning
     mention:
+      action: Svar
       body: 'Du nämndes av %{name} in:'
       subject: Du nämndes av %{name}
+      title: Ny omnämning
     reblog:
-      body: 'Din status boostades av %{name}:'
-      subject: "%{name} boostade din status"
+      body: 'Din status knuffades av %{name}:'
+      subject: "%{name} knuffade din status"
+      title: Ny knuff
   number:
     human:
       decimal_units:
@@ -413,7 +533,7 @@ sv:
           quadrillion: Q
           thousand: K
           trillion: T
-          unit: ''
+          unit: enhet
   pagination:
     next: Nästa
     prev: Tidigare
@@ -450,12 +570,14 @@ sv:
       blackberry: Blackberry
       chrome: Chrome
       edge: Microsoft Edge
+      electron: Electron
       firefox: Firefox
       generic: Okänd browser
       ie: Internet Explorer
       micro_messenger: MicroMessenger
       nokia: Nokia S40 Ovi Browser
       opera: Opera
+      otter: Otter
       phantom_js: PhantomJS
       qq: QQ Browser
       safari: Safari
@@ -463,7 +585,7 @@ sv:
       weibo: Weibo
     current_session: Nuvarande session
     description: "%{browser} på %{platform}"
-    explanation: Detta är inloggade webbläsare på Mastodon just nu
+    explanation: Detta är inloggade webbläsare på Mastodon just nu.
     ip: IP
     platforms:
       adobe_air: Adobe Air
@@ -490,20 +612,22 @@ sv:
     export: Data export
     followers: Auktoriserade följare
     import: Import
+    migrate: Kontoflytt
     notifications: Meddelanden
     preferences: Inställningar
     settings: Inställningar
-    two_factor_authentication: Två-faktor autentisering
+    two_factor_authentication: Tvåstegsautentisering
     your_apps: Dina applikationer
   statuses:
     open_in_web: Öppna på webben
     over_character_limit: teckengräns på %{max} har överskridits
     pin_errors:
-      limit: För många tootar fästa
+      limit: Du har redan fäst det maximala antalet toots
       ownership: Någon annans toot kan inte fästas
       private: Icke-offentliga toot kan inte fästas
       reblog: En boost kan inte fästas
     show_more: Visa mer
+    title: '%{name}: "%{quote}"'
     visibilities:
       private: Endast följare
       private_long: Visa endast till följare
@@ -533,10 +657,10 @@ sv:
       <p>Vilken som helst information vi samlar in från dig kan användas på något av följande sätt:</p>
 
       <ul>
-        <li>För att personifiera din upplevelse & mdash; Din information hjälper oss att bättre svara på dina individuella behov.</li>
-        <li>För att förbättra vår webbplats & mdash; Vi strävar kontinuerligt efter att förbättra våra erbjudanden på webbplatsen baserat på information och feedback vi mottar från dig.</li>
-        <li>För att förbättra kundtjänst & mdash; Din information hjälper oss att effektivt svara på dina kundserviceförfrågningar och supportbehov.</li>
-        <li>För att skicka periodiska e-postmeddelanden & mdash; Den e-postadress du anger kan användas för att skicka information, meddelanden som du begär om ändringar i ämnen eller som svar på ditt användarnamn, svara på förfrågningar och / eller andra förfrågningar eller frågor.</li>
+        <li>För att personifiera din upplevelse &mdash; Din information hjälper oss att bättre svara på dina individuella behov.</li>
+        <li>För att förbättra vår webbplats &mdash; Vi strävar kontinuerligt efter att förbättra våra erbjudanden på webbplatsen baserat på information och feedback vi mottar från dig.</li>
+        <li>För att förbättra kundtjänst &mdash; Din information hjälper oss att effektivt svara på dina kundserviceförfrågningar och supportbehov.</li>
+        <li>För att skicka periodiska e-postmeddelanden &mdash; Den e-postadress du anger kan användas för att skicka information, meddelanden som du begär om ändringar i ämnen eller som svar på ditt användarnamn, svara på förfrågningar och / eller andra förfrågningar eller frågor.</li>
       </ul>
 
       <h3 id="protect">Hur skyddar vi din information?</h3>
@@ -586,6 +710,8 @@ sv:
 
       <p>Ursprungligen anpassad från <a href="https://github.com/discourse/discourse">Discourse integritetspolicy</a>.</p>
     title: "%{instance} Användarvillkor och Sekretesspolicy"
+  themes:
+    default: Mastodon
   time:
     formats:
       default: "%b %d, %Y, %H:%M"
@@ -605,6 +731,25 @@ sv:
     recovery_instructions_html: Om du någonsin tappar åtkomst till din telefon kan du använda någon av återställningskoderna nedan för att återställa åtkomst till ditt konto. <strong> Håll återställningskoderna säkra </strong>. Du kan till exempel skriva ut dem och lagra dem med andra viktiga dokument.
     setup: Ställ in
     wrong_code: Den angivna koden var ogiltig! Är servertid och enhetstid korrekt?
+  user_mailer:
+    welcome:
+      edit_profile_action: Profilinställning
+      edit_profile_step: Du kan anpassa din profil genom att ladda upp en avatar, rubrik, ändra ditt visningsnamn och mer. Om du vill granska nya följare innan de får följa dig kan du låsa ditt konto.
+      explanation: Här är några tips för att komma igång
+      final_action: Börja posta
+      final_step: 'Börja posta! Även utan anhängare kan dina offentliga meddelanden ses av andra, till exempel på den lokala tidslinjen och i hashtags. Du får gärna presentera dig via hashtaggen #introductions.'
+      full_handle: Ditt fullständiga användarnamn/mastodonadress
+      full_handle_hint: Det här är vad du skulle berätta för dina vänner så att de kan meddela eller följa dig från en annan instans.
+      review_preferences_action: Ändra inställningar
+      review_preferences_step: Se till att du ställer in dina inställningar, t.ex. vilka e-postmeddelanden du vill ta emot eller vilken integritetsnivå du vill att dina inlägg ska vara. Om du inte har åksjuka, kan du välja att aktivera automatisk uppspelning av GIF-bilder.
+      subject: Välkommen till Mastodon
+      tip_bridge_html: Om du kommer från Twitter kan du hitta dina vänner på Mastodon genom att använda <a href="%{bridge_url}">bridge-appen</a>. Det fungerar dock bara om de också har använt bridge-appen!
+      tip_federated_timeline: Den förenade tidslinjen är en störtflodsvy av Mastodon-nätverket. Men det inkluderar bara människor som dina grannar följer, så det är inte komplett.
+      tip_following: Du följer din servers administratör(er) som standard. För att hitta fler intressanta personer, kolla de lokala och förenade tidslinjerna.
+      tip_local_timeline: Den lokala tidslinjen är en störtflodsvy av personer på% {instance}. Det här är dina närmaste grannar!
+      tip_mobile_webapp: Om din mobila webbläsare erbjuder dig att lägga till Mastodon till ditt hemskärm kan du få push-meddelanden. Det fungerar som en inbyggd app på många sätt!
+      tips: Tips
+      title: Välkommen ombord, %{name}!
   users:
     invalid_email: E-postadressen är ogiltig
     invalid_otp_token: Ogiltig tvåfaktorkod
diff --git a/config/locales/th.yml b/config/locales/th.yml
index 737b3aa95..45fe1e475 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -157,7 +157,6 @@ th:
   applications:
     invalid_url: URL ที่ระบุไม่ถูกตั้ง
   auth:
-    change_password: Credentials
     didnt_get_confirmation: Didn't receive confirmation instructions?
     forgot_password: คุณลืมพาสเวริ์ดใช่ัม้ย?
     login: ล๊อคอิน
@@ -165,6 +164,7 @@ th:
     register: สมัคร
     resend_confirmation: ส่งขั้นตอนวิธีการยืนยันใหม่อีกครั้ง
     reset_password: เปลี่ยนรหัสผ่าน
+    security: Credentials
     set_new_password: ตั้งรหัสผ่านใหม่
   authorize_follow:
     error: Unfortunately, there was an error looking up the remote account
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 23b4d7a24..ee0e33074 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -156,7 +156,6 @@ tr:
   applications:
     invalid_url: Verilen URL geçerli değil
   auth:
-    change_password: Kimlik bilgileri
     didnt_get_confirmation: Hesap doğrulama mailini almadınız mı?
     forgot_password: Parolanızı unuttunuz mu?
     login: Giriş yap
@@ -164,6 +163,7 @@ tr:
     register: Üye ol
     resend_confirmation: Doğrulama mailini tekrar gönder
     reset_password: Parolayı değiştir
+    security: Kimlik bilgileri
     set_new_password: Yeni parola oluştur
   authorize_follow:
     error: Uzak hesap aranırken bir hata oluştu.
diff --git a/config/locales/uk.yml b/config/locales/uk.yml
index 0ddfa9190..4c1c66b31 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -147,7 +147,6 @@ uk:
   applications:
     invalid_url: Введена URL неправильна
   auth:
-    change_password: Зміна паролю
     didnt_get_confirmation: Ви не отримали інструкції з підтвердження?
     forgot_password: Забули свій пароль?
     login: Увійти
@@ -155,6 +154,7 @@ uk:
     register: Зареєструватися
     resend_confirmation: Повторно відправити інструкції з підтвердження
     reset_password: Скинути пароль
+    security: Зміна паролю
     set_new_password: Встановити новий пароль
   authorize_follow:
     error: На жаль, при пошуку віддаленого аккаунту виникла помилка
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index 7deb241a1..1254651cd 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -23,7 +23,6 @@ zh-CN:
       real_conversation_title: 为真正的交流而生
       within_reach_body: 通过一个面向开发者友好的 API 生态系统,Mastodon 让你可以随时随地通过众多 iOS、Android 以及其他平台的应用与朋友们保持联系。
       within_reach_title: 始终触手可及
-    find_another_instance: 寻找另一个实例
     generic_description: "%{domain} 是这个庞大网络中的一台服务器"
     hosted_on: 一个在 %{domain} 上运行的 Mastodon 实例
     learn_more: 详细了解
@@ -352,7 +351,6 @@ zh-CN:
     your_token: 你的访问令牌
   auth:
     agreement_html: 注册即表示你同意遵守<a href="%{rules_path}">本实例的相关规定</a>和<a href="%{terms_path}">我们的使用条款</a>。
-    change_password: 帐户安全
     delete_account: 删除帐户
     delete_account_html: 如果你想删除你的帐户,请<a href="%{path}">点击这里继续</a>。你需要确认你的操作。
     didnt_get_confirmation: 没有收到确认邮件?
@@ -365,6 +363,7 @@ zh-CN:
     register: 注册
     resend_confirmation: 重新发送确认邮件
     reset_password: 重置密码
+    security: 帐户安全
     set_new_password: 设置新密码
   authorize_follow:
     error: 对不起,寻找这个跨站用户时出错
diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml
index ed73b7244..e7ab347a1 100644
--- a/config/locales/zh-HK.yml
+++ b/config/locales/zh-HK.yml
@@ -156,7 +156,6 @@ zh-HK:
   applications:
     invalid_url: 所提供的網址不正確
   auth:
-    change_password: 登入資訊
     didnt_get_confirmation: 沒有收到確認指示電郵?
     forgot_password: 忘記了密碼?
     login: 登入
@@ -164,6 +163,7 @@ zh-HK:
     register: 登記
     resend_confirmation: 重發確認指示電郵
     reset_password: 重設密碼
+    security: 登入資訊
     set_new_password: 設定新密碼
   authorize_follow:
     error: 對不起,尋找這個跨站用戶的過程發生錯誤
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index bd9f85840..2fec09ed8 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -127,7 +127,6 @@ zh-TW:
   applications:
     invalid_url: 網址不正確
   auth:
-    change_password: 登入資訊
     didnt_get_confirmation: 沒有收到驗證信?
     forgot_password: 忘記密碼?
     login: 登入
@@ -137,6 +136,7 @@ zh-TW:
     register: 註冊
     resend_confirmation: 重寄驗證信
     reset_password: 重設密碼
+    security: 登入資訊
     set_new_password: 設定新密碼
   authorize_follow:
     error: 對不起,搜尋遠端使用者出現錯誤
diff --git a/config/navigation.rb b/config/navigation.rb
index b08b1769d..d80546c35 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -9,7 +9,7 @@ SimpleNavigation::Configuration.run do |navigation|
       settings.item :preferences, safe_join([fa_icon('sliders fw'), t('settings.preferences')]), settings_preferences_url
       settings.item :keyword_mutes, safe_join([fa_icon('volume-off fw'), t('settings.keyword_mutes')]), settings_keyword_mutes_url
       settings.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_notifications_url
-      settings.item :password, safe_join([fa_icon('lock fw'), t('auth.change_password')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete}
+      settings.item :password, safe_join([fa_icon('lock fw'), t('auth.security')]), edit_user_registration_url, highlights_on: %r{/auth/edit|/settings/delete}
       settings.item :two_factor_authentication, safe_join([fa_icon('mobile fw'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_url, highlights_on: %r{/settings/two_factor_authentication}
       settings.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url
       settings.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url
diff --git a/config/routes.rb b/config/routes.rb
index f45684519..8fd810c10 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -24,9 +24,11 @@ Rails.application.routes.draw do
 
   devise_scope :user do
     get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite
+    match '/auth/finish_signup' => 'auth/confirmations#finish_signup', via: [:get, :patch], as: :finish_signup
   end
 
   devise_for :users, path: 'auth', controllers: {
+    omniauth_callbacks: 'auth/omniauth_callbacks',
     sessions:           'auth/sessions',
     registrations:      'auth/registrations',
     passwords:          'auth/passwords',
@@ -56,8 +58,10 @@ Rails.application.routes.draw do
     resources :following, only: [:index], controller: :following_accounts
     resource :follow, only: [:create], controller: :account_follow
     resource :unfollow, only: [:create], controller: :account_unfollow
+
     resource :outbox, only: [:show], module: :activitypub
     resource :inbox, only: [:create], module: :activitypub
+    resources :collections, only: [:show], module: :activitypub
   end
 
   resource :inbox, only: [:create], module: :activitypub
@@ -81,7 +85,7 @@ Rails.application.routes.draw do
     resource :notifications, only: [:show, :update]
     resource :import, only: [:show, :create]
 
-    resource :export, only: [:show]
+    resource :export, only: [:show, :create]
     namespace :exports, constraints: { format: :csv } do
       resources :follows, only: :index, controller: :following_accounts
       resources :blocks, only: :index, controller: :blocked_accounts
@@ -110,7 +114,10 @@ Rails.application.routes.draw do
     resources :sessions, only: [:destroy]
   end
 
-  resources :media,  only: [:show]
+  resources :media, only: [:show] do
+    get :player
+  end
+
   resources :tags,   only: [:show]
   resources :emojis, only: [:show]
   resources :invites, only: [:index, :create, :destroy]
diff --git a/config/settings.yml b/config/settings.yml
index 507b7c066..580a20895 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -25,6 +25,7 @@ defaults: &defaults
   favourite_modal: false
   delete_modal: true
   auto_play_gif: false
+  display_sensitive_media: false
   reduce_motion: false
   system_font_ui: false
   noindex: false
@@ -51,6 +52,7 @@ defaults: &defaults
   bootstrap_timeline_accounts: ''
   activity_api_enabled: true
   peers_api_enabled: true
+  show_known_fediverse_at_about_page: true
 development:
   <<: *defaults
 
diff --git a/config/sidekiq.yml b/config/sidekiq.yml
index bfe29b8f8..244e9ea48 100644
--- a/config/sidekiq.yml
+++ b/config/sidekiq.yml
@@ -30,3 +30,6 @@
   email_scheduler:
     cron: '0 10 * * 2'
     class: Scheduler::EmailScheduler
+  backup_cleanup_scheduler:
+    cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
+    class: Scheduler::BackupCleanupScheduler
diff --git a/config/webpack/configuration.js b/config/webpack/configuration.js
index f6b4d4c61..47883b68d 100644
--- a/config/webpack/configuration.js
+++ b/config/webpack/configuration.js
@@ -73,7 +73,7 @@ function formatPublicPath(host = '', path = '') {
 
 const output = {
   path: resolve('public', settings.public_output_path),
-  publicPath: formatPublicPath(env.ASSET_HOST || env.LOCAL_DOMAIN, settings.public_output_path),
+  publicPath: formatPublicPath(env.CDN_HOST, settings.public_output_path),
 };
 
 module.exports = {
diff --git a/db/migrate/20180109143959_add_remember_token_to_users.rb b/db/migrate/20180109143959_add_remember_token_to_users.rb
new file mode 100644
index 000000000..662905bcb
--- /dev/null
+++ b/db/migrate/20180109143959_add_remember_token_to_users.rb
@@ -0,0 +1,5 @@
+class AddRememberTokenToUsers < ActiveRecord::Migration[5.1]
+  def change
+    add_column :users, :remember_token, :string, null: true
+  end
+end
diff --git a/db/migrate/20180204034416_create_identities.rb b/db/migrate/20180204034416_create_identities.rb
new file mode 100644
index 000000000..f6f5da910
--- /dev/null
+++ b/db/migrate/20180204034416_create_identities.rb
@@ -0,0 +1,11 @@
+class CreateIdentities < ActiveRecord::Migration[5.0]
+  def change
+    create_table :identities do |t|
+      t.references :user, foreign_key: { on_delete: :cascade }
+      t.string :provider, null: false, default: ''
+      t.string :uid, null: false, default: ''
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20180206000000_change_user_id_nonnullable.rb b/db/migrate/20180206000000_change_user_id_nonnullable.rb
new file mode 100644
index 000000000..4eecb6154
--- /dev/null
+++ b/db/migrate/20180206000000_change_user_id_nonnullable.rb
@@ -0,0 +1,6 @@
+class ChangeUserIdNonnullable < ActiveRecord::Migration[5.1]
+  def change
+    change_column_null :invites, :user_id, false
+    change_column_null :web_settings, :user_id, false
+  end
+end
diff --git a/db/migrate/20180211015820_create_backups.rb b/db/migrate/20180211015820_create_backups.rb
new file mode 100644
index 000000000..9725a3e9f
--- /dev/null
+++ b/db/migrate/20180211015820_create_backups.rb
@@ -0,0 +1,11 @@
+class CreateBackups < ActiveRecord::Migration[5.1]
+  def change
+    create_table :backups do |t|
+      t.references :user, foreign_key: { on_delete: :nullify }
+      t.attachment :dump
+      t.boolean :processed, null: false, default: false
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20180304013859_add_featured_collection_url_to_accounts.rb b/db/migrate/20180304013859_add_featured_collection_url_to_accounts.rb
new file mode 100644
index 000000000..e0b8ed5cc
--- /dev/null
+++ b/db/migrate/20180304013859_add_featured_collection_url_to_accounts.rb
@@ -0,0 +1,5 @@
+class AddFeaturedCollectionUrlToAccounts < ActiveRecord::Migration[5.1]
+  def change
+    add_column :accounts, :featured_collection_url, :string
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 11ca12235..70420d396 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20180106000232) do
+ActiveRecord::Schema.define(version: 20180304013859) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -73,6 +73,7 @@ ActiveRecord::Schema.define(version: 20180106000232) do
     t.integer "protocol", default: 0, null: false
     t.boolean "memorial", default: false, null: false
     t.bigint "moved_to_account_id"
+    t.string "featured_collection_url"
     t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
     t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower"
     t.index ["uri"], name: "index_accounts_on_uri"
@@ -92,6 +93,18 @@ ActiveRecord::Schema.define(version: 20180106000232) do
     t.index ["target_type", "target_id"], name: "index_admin_action_logs_on_target_type_and_target_id"
   end
 
+  create_table "backups", force: :cascade do |t|
+    t.bigint "user_id"
+    t.string "dump_file_name"
+    t.string "dump_content_type"
+    t.integer "dump_file_size"
+    t.datetime "dump_updated_at"
+    t.boolean "processed", default: false, null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["user_id"], name: "index_backups_on_user_id"
+  end
+
   create_table "blocks", force: :cascade do |t|
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
@@ -182,6 +195,15 @@ ActiveRecord::Schema.define(version: 20180106000232) do
     t.index ["account_id"], name: "index_glitch_keyword_mutes_on_account_id"
   end
 
+  create_table "identities", id: :serial, force: :cascade do |t|
+    t.integer "user_id"
+    t.string "provider", default: "", null: false
+    t.string "uid", default: "", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["user_id"], name: "index_identities_on_user_id"
+  end
+
   create_table "imports", force: :cascade do |t|
     t.integer "type", null: false
     t.boolean "approved", default: false, null: false
@@ -195,7 +217,7 @@ ActiveRecord::Schema.define(version: 20180106000232) do
   end
 
   create_table "invites", force: :cascade do |t|
-    t.bigint "user_id"
+    t.bigint "user_id", null: false
     t.string "code", default: "", null: false
     t.datetime "expires_at"
     t.integer "max_uses"
@@ -496,6 +518,7 @@ ActiveRecord::Schema.define(version: 20180106000232) do
     t.boolean "disabled", default: false, null: false
     t.boolean "moderator", default: false, null: false
     t.bigint "invite_id"
+    t.string "remember_token"
     t.index ["account_id"], name: "index_users_on_account_id"
     t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
     t.index ["email"], name: "index_users_on_email", unique: true
@@ -516,7 +539,7 @@ ActiveRecord::Schema.define(version: 20180106000232) do
     t.json "data"
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
-    t.bigint "user_id"
+    t.bigint "user_id", null: false
     t.index ["user_id"], name: "index_web_settings_on_user_id", unique: true
   end
 
@@ -525,6 +548,7 @@ ActiveRecord::Schema.define(version: 20180106000232) do
   add_foreign_key "account_moderation_notes", "accounts", column: "target_account_id"
   add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify
   add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade
+  add_foreign_key "backups", "users", on_delete: :nullify
   add_foreign_key "blocks", "accounts", column: "target_account_id", name: "fk_9571bfabc1", on_delete: :cascade
   add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade
   add_foreign_key "conversation_mutes", "accounts", name: "fk_225b4212bb", on_delete: :cascade
@@ -536,6 +560,7 @@ ActiveRecord::Schema.define(version: 20180106000232) do
   add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade
   add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
   add_foreign_key "glitch_keyword_mutes", "accounts", on_delete: :cascade
+  add_foreign_key "identities", "users", on_delete: :cascade
   add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
   add_foreign_key "invites", "users", on_delete: :cascade
   add_foreign_key "list_accounts", "accounts", on_delete: :cascade
diff --git a/docker-compose.yml b/docker-compose.yml
index f280d4ecc..836cb00b8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -4,6 +4,8 @@ services:
   db:
     restart: always
     image: postgres:9.6-alpine
+    networks:
+      - internal_network
 ### Uncomment to enable DB persistance
 #    volumes:
 #      - ./postgres:/var/lib/postgresql/data
@@ -11,21 +13,38 @@ services:
   redis:
     restart: always
     image: redis:4.0-alpine
+    networks:
+      - internal_network
 ### Uncomment to enable REDIS persistance
 #    volumes:
 #      - ./redis:/data
 
+#  es:
+#    restart: always
+#    image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.1.3
+#    environment:
+#      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
+#    networks:
+#      - internal_network
+#### Uncomment to enable ES persistance
+##    volumes:
+##      - ./elasticsearch:/usr/share/elasticsearch/data
+
   web:
     build: .
-    image: gargron/mastodon
+    image: tootsuite/mastodon
     restart: always
     env_file: .env.production
     command: bundle exec rails s -p 3000 -b '0.0.0.0'
+    networks:
+      - external_network
+      - internal_network
     ports:
       - "3000:3000"
     depends_on:
       - db
       - redis
+#      - es
     volumes:
       - ./public/assets:/mastodon/public/assets
       - ./public/packs:/mastodon/public/packs
@@ -33,10 +52,13 @@ services:
 
   streaming:
     build: .
-    image: gargron/mastodon
+    image: tootsuite/mastodon
     restart: always
     env_file: .env.production
-    command: npm run start
+    command: yarn start
+    networks:
+      - external_network
+      - internal_network
     ports:
       - "4000:4000"
     depends_on:
@@ -45,12 +67,21 @@ services:
 
   sidekiq:
     build: .
-    image: gargron/mastodon
+    image: tootsuite/mastodon
     restart: always
     env_file: .env.production
     command: bundle exec sidekiq -q default -q mailers -q pull -q push
     depends_on:
       - db
       - redis
+    networks:
+      - external_network
+      - internal_network
     volumes:
+      - ./public/packs:/mastodon/public/packs
       - ./public/system:/mastodon/public/system
+
+networks:
+  external_network:
+  internal_network:
+    internal: true
diff --git a/docker_entrypoint.sh b/docker_entrypoint.sh
deleted file mode 100644
index 1af5dde64..000000000
--- a/docker_entrypoint.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-
-### 1. Adds local user (UID and GID are provided from environment variables).
-### 2. Updates permissions, except for ./public/system (should be chown on previous installations).
-### 3. Executes the command as that user.
-
-echo "Creating mastodon user (UID : ${UID} and GID : ${GID})..."
-addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon
-
-echo "Updating permissions..."
-find /mastodon -path /mastodon/public/system -prune -o -not -user mastodon -not -group mastodon -print0 | xargs -0 chown -f mastodon:mastodon
-
-echo "Executing process..."
-LD_PRELOAD=/lib/stack-fix.so exec su-exec mastodon:mastodon /sbin/tini -- "$@"
diff --git a/lib/devise/ldap_authenticatable.rb b/lib/devise/ldap_authenticatable.rb
new file mode 100644
index 000000000..531abdbbe
--- /dev/null
+++ b/lib/devise/ldap_authenticatable.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+if ENV['LDAP_ENABLED'] == 'true'
+  require 'net/ldap'
+  require 'devise/strategies/authenticatable'
+
+  module Devise
+    module Strategies
+      class LdapAuthenticatable < Authenticatable
+        def authenticate!
+          if params[:user]
+            ldap = Net::LDAP.new(
+              host: Devise.ldap_host,
+              port: Devise.ldap_port,
+              base: Devise.ldap_base,
+              encryption: {
+                method: Devise.ldap_method,
+                tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS,
+              },
+              auth: {
+                method: :simple,
+                username: Devise.ldap_bind_dn,
+                password: Devise.ldap_password,
+              },
+              connect_timeout: 10
+            )
+
+            if (user_info = ldap.bind_as(base: Devise.ldap_base, filter: "(#{Devise.ldap_uid}=#{email})", password: password))
+              user = User.ldap_get_user(user_info.first)
+              success!(user)
+            else
+              return fail(:invalid_login)
+            end
+          end
+        end
+
+        def email
+          params[:user][:email]
+        end
+
+        def password
+          params[:user][:password]
+        end
+      end
+    end
+  end
+
+  Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
+end
diff --git a/lib/mastodon/premailer_webpack_strategy.rb b/lib/mastodon/premailer_webpack_strategy.rb
index 84d83cc66..56ef09c1a 100644
--- a/lib/mastodon/premailer_webpack_strategy.rb
+++ b/lib/mastodon/premailer_webpack_strategy.rb
@@ -2,16 +2,21 @@
 
 module PremailerWebpackStrategy
   def load(url)
-    public_path_host = ENV['ASSET_HOST'] || ENV['LOCAL_DOMAIN']
-    url = url.gsub(/\A\/\/#{public_path_host}/, '')
+    asset_host = ENV['CDN_HOST'] || ENV['WEB_DOMAIN'] || ENV['LOCAL_DOMAIN']
 
     if Webpacker.dev_server.running?
-      url = File.join("#{Webpacker.dev_server.protocol}://#{Webpacker.dev_server.host_with_port}", url)
-      HTTP.get(url).to_s
-    else
-      url = url[1..-1] if url.start_with?('/')
-      File.read(Rails.root.join('public', url))
+      asset_host = "#{Webpacker.dev_server.protocol}://#{Webpacker.dev_server.host_with_port}"
+      url        = File.join(asset_host, url)
     end
+
+    css = if url.start_with?('http')
+            HTTP.get(url).to_s
+          else
+            url = url[1..-1] if url.start_with?('/')
+            File.read(Rails.root.join('public', url))
+          end
+
+    css.gsub(/url\(\//, "url(#{asset_host}/")
   end
 
   module_function :load
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index 098ab46ac..3ba96731d 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -9,7 +9,7 @@ module Mastodon
     end
 
     def minor
-      2
+      3
     end
 
     def patch
@@ -21,7 +21,7 @@ module Mastodon
     end
 
     def flags
-      'rc1'
+      ''
     end
 
     def to_a
diff --git a/lib/paperclip/gif_transcoder.rb b/lib/paperclip/gif_transcoder.rb
index 629e37581..62787983c 100644
--- a/lib/paperclip/gif_transcoder.rb
+++ b/lib/paperclip/gif_transcoder.rb
@@ -16,7 +16,7 @@ module Paperclip
 
       final_file = Paperclip::Transcoder.make(file, options, attachment)
 
-      attachment.instance.file_file_name    = 'media.mp4'
+      attachment.instance.file_file_name    = File.basename(attachment.instance.file_file_name, '.*') + '.mp4'
       attachment.instance.file_content_type = 'video/mp4'
       attachment.instance.type              = MediaAttachment.types[:gifv]
 
diff --git a/lib/paperclip/lazy_thumbnail.rb b/lib/paperclip/lazy_thumbnail.rb
new file mode 100644
index 000000000..aafa21343
--- /dev/null
+++ b/lib/paperclip/lazy_thumbnail.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Paperclip
+  class LazyThumbnail < Paperclip::Thumbnail
+    def make
+      return File.open(@file.path) unless needs_convert?
+
+      min_side = [@current_geometry.width, @current_geometry.height].min
+      options[:geometry] = "#{min_side.to_i}x#{min_side.to_i}#" if @target_geometry.square? && min_side < @target_geometry.width
+
+      Paperclip::Thumbnail.make(file, options, attachment)
+    end
+
+    private
+
+    def needs_convert?
+      needs_different_geometry? || needs_different_format?
+    end
+
+    def needs_different_geometry?
+      !@target_geometry.nil? && @current_geometry.width != @target_geometry.width && @current_geometry.height != @target_geometry.height
+    end
+
+    def needs_different_format?
+      @format.present? && @current_format != @format
+    end
+  end
+end
diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake
index f60c1b9f2..b642510a1 100644
--- a/lib/tasks/assets.rake
+++ b/lib/tasks/assets.rake
@@ -1,15 +1,13 @@
 # frozen_string_literal: true
 
 def render_static_page(action, dest:, **opts)
-  I18n.with_locale(ENV['DEFAULT_LOCALE'] || I18n.default_locale) do
-    html = ApplicationController.render(action, opts)
-    File.write(dest, html)
-  end
+  html = ApplicationController.render(action, opts)
+  File.write(dest, html)
 end
 
 namespace :assets do
   desc 'Generate static pages'
-  task :generate_static_pages do
+  task generate_static_pages: :environment do
     render_static_page 'errors/500', layout: 'error', dest: Rails.root.join('public', 'assets', '500.html')
   end
 end
diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index 486c035de..d6c9e2d01 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -4,6 +4,370 @@ require 'optparse'
 require 'colorize'
 
 namespace :mastodon do
+  desc 'Configure the instance for production use'
+  task :setup do
+    prompt = TTY::Prompt.new
+    env    = {}
+
+    begin
+      prompt.say('Your instance is identified by its domain name. Changing it afterward will break things.')
+      env['LOCAL_DOMAIN'] = prompt.ask('Domain name:') do |q|
+        q.required true
+        q.modify :strip
+        q.validate(/\A[a-z0-9\.\-]+\z/i)
+        q.messages[:valid?] = 'Invalid domain. If you intend to use unicode characters, enter punycode here'
+      end
+
+      prompt.say "\n"
+
+      prompt.say('Single user mode disables registrations and redirects the landing page to your public profile.')
+      env['SINGLE_USER_MODE'] = prompt.yes?('Do you want to enable single user mode?', default: false)
+
+      %w(SECRET_KEY_BASE OTP_SECRET).each do |key|
+        env[key] = SecureRandom.hex(64)
+      end
+
+      vapid_key = Webpush.generate_key
+
+      env['VAPID_PRIVATE_KEY'] = vapid_key.private_key
+      env['VAPID_PUBLIC_KEY']  = vapid_key.public_key
+
+      prompt.say "\n"
+
+      using_docker        = prompt.yes?('Are you using Docker to run Mastodon?')
+      db_connection_works = false
+
+      prompt.say "\n"
+
+      loop do
+        env['DB_HOST'] = prompt.ask('PostgreSQL host:') do |q|
+          q.required true
+          q.default using_docker ? 'db' : '/var/run/postgresql'
+          q.modify :strip
+        end
+
+        env['DB_PORT'] = prompt.ask('PostgreSQL port:') do |q|
+          q.required true
+          q.default 5432
+          q.convert :int
+        end
+
+        env['DB_NAME'] = prompt.ask('Name of PostgreSQL database:') do |q|
+          q.required true
+          q.default using_docker ? 'postgres' : 'mastodon_production'
+          q.modify :strip
+        end
+
+        env['DB_USER'] = prompt.ask('Name of PostgreSQL user:') do |q|
+          q.required true
+          q.default using_docker ? 'postgres' : 'mastodon'
+          q.modify :strip
+        end
+
+        env['DB_PASS'] = prompt.ask('Password of PostgreSQL user:') do |q|
+          q.echo false
+        end
+
+        # The chosen database may not exist yet. Connect to default database
+        # to avoid "database does not exist" error.
+        db_options = {
+          adapter: :postgresql,
+          database: 'postgres',
+          host: env['DB_HOST'],
+          port: env['DB_PORT'],
+          user: env['DB_USER'],
+          password: env['DB_PASS'],
+        }
+
+        begin
+          ActiveRecord::Base.establish_connection(db_options)
+          ActiveRecord::Base.connection
+          prompt.ok 'Database configuration works! 🎆'
+          db_connection_works = true
+          break
+        rescue StandardError => e
+          prompt.error 'Database connection could not be established with this configuration, try again.'
+          prompt.error e.message
+          break unless prompt.yes?('Try again?')
+        end
+      end
+
+      prompt.say "\n"
+
+      loop do
+        env['REDIS_HOST'] = prompt.ask('Redis host:') do |q|
+          q.required true
+          q.default using_docker ? 'redis' : 'localhost'
+          q.modify :strip
+        end
+
+        env['REDIS_PORT'] = prompt.ask('Redis port:') do |q|
+          q.required true
+          q.default 6379
+          q.convert :int
+        end
+
+        redis_options = {
+          host: env['REDIS_HOST'],
+          port: env['REDIS_PORT'],
+          driver: :hiredis,
+        }
+
+        begin
+          redis = Redis.new(redis_options)
+          redis.ping
+          prompt.ok 'Redis configuration works! 🎆'
+          break
+        rescue StandardError => e
+          prompt.error 'Redis connection could not be established with this configuration, try again.'
+          prompt.error e.message
+          break unless prompt.yes?('Try again?')
+        end
+      end
+
+      prompt.say "\n"
+
+      if prompt.yes?('Do you want to store uploaded files on the cloud?', default: false)
+        case prompt.select('Provider', ['Amazon S3', 'Wasabi', 'Minio'])
+        when 'Amazon S3'
+          env['S3_ENABLED']  = 'true'
+          env['S3_PROTOCOL'] = 'https'
+
+          env['S3_BUCKET'] = prompt.ask('S3 bucket name:') do |q|
+            q.required true
+            q.default "files.#{env['LOCAL_DOMAIN']}"
+            q.modify :strip
+          end
+
+          env['S3_REGION'] = prompt.ask('S3 region:') do |q|
+            q.required true
+            q.default 'us-east-1'
+            q.modify :strip
+          end
+
+          env['S3_HOSTNAME'] = prompt.ask('S3 hostname:') do |q|
+            q.required true
+            q.default 's3-us-east-1.amazonaws.com'
+            q.modify :strip
+          end
+
+          env['AWS_ACCESS_KEY_ID'] = prompt.ask('S3 access key:') do |q|
+            q.required true
+            q.modify :strip
+          end
+
+          env['AWS_SECRET_ACCESS_KEY'] = prompt.ask('S3 secret key:') do |q|
+            q.required true
+            q.modify :strip
+          end
+        when 'Wasabi'
+          env['S3_ENABLED']  = 'true'
+          env['S3_PROTOCOL'] = 'https'
+          env['S3_REGION']   = 'us-east-1'
+          env['S3_HOSTNAME'] = 's3.wasabisys.com'
+          env['S3_ENDPOINT'] = 'https://s3.wasabisys.com/'
+
+          env['S3_BUCKET'] = prompt.ask('Wasabi bucket name:') do |q|
+            q.required true
+            q.default "files.#{env['LOCAL_DOMAIN']}"
+            q.modify :strip
+          end
+
+          env['AWS_ACCESS_KEY_ID'] = prompt.ask('Wasabi access key:') do |q|
+            q.required true
+            q.modify :strip
+          end
+
+          env['AWS_SECRET_ACCESS_KEY'] = prompt.ask('Wasabi secret key:') do |q|
+            q.required true
+            q.modify :strip
+          end
+        when 'Minio'
+          env['S3_ENABLED']  = 'true'
+          env['S3_PROTOCOL'] = 'https'
+          env['S3_REGION']   = 'us-east-1'
+
+          env['S3_ENDPOINT'] = prompt.ask('Minio endpoint URL:') do |q|
+            q.required true
+            q.modify :strip
+          end
+
+          env['S3_PROTOCOL'] = env['S3_ENDPOINT'].start_with?('https') ? 'https' : 'http'
+          env['S3_HOSTNAME'] = env['S3_ENDPOINT'].gsub(/\Ahttps?:\/\//, '')
+
+          env['S3_BUCKET'] = prompt.ask('Minio bucket name:') do |q|
+            q.required true
+            q.default "files.#{env['LOCAL_DOMAIN']}"
+            q.modify :strip
+          end
+
+          env['AWS_ACCESS_KEY_ID'] = prompt.ask('Minio access key:') do |q|
+            q.required true
+            q.modify :strip
+          end
+
+          env['AWS_SECRET_ACCESS_KEY'] = prompt.ask('Minio secret key:') do |q|
+            q.required true
+            q.modify :strip
+          end
+        end
+
+        if prompt.yes?('Do you want to access the uploaded files from your own domain?')
+          env['S3_CLOUDFRONT_HOST'] = prompt.ask('Domain for uploaded files:') do |q|
+            q.required true
+            q.default "files.#{env['LOCAL_DOMAIN']}"
+            q.modify :strip
+          end
+        end
+      end
+
+      prompt.say "\n"
+
+      loop do
+        env['SMTP_SERVER'] = prompt.ask('SMTP server:') do |q|
+          q.required true
+          q.default 'smtp.mailgun.org'
+          q.modify :strip
+        end
+
+        env['SMTP_PORT'] = prompt.ask('SMTP port:') do |q|
+          q.required true
+          q.default 587
+          q.convert :int
+        end
+
+        env['SMTP_LOGIN'] = prompt.ask('SMTP username:') do |q|
+          q.modify :strip
+        end
+
+        env['SMTP_PASSWORD'] = prompt.ask('SMTP password:') do |q|
+          q.echo false
+        end
+
+        env['SMTP_FROM_ADDRESS'] = prompt.ask('E-mail address to send e-mails "from":') do |q|
+          q.required true
+          q.default "Mastodon <notifications@#{env['LOCAL_DOMAIN']}>"
+          q.modify :strip
+        end
+
+        break unless prompt.yes?('Send a test e-mail with this configuration right now?')
+
+        send_to = prompt.ask('Send test e-mail to:', required: true)
+
+        begin
+          ActionMailer::Base.smtp_settings = {
+            :port                 => env['SMTP_PORT'],
+            :address              => env['SMTP_SERVER'],
+            :user_name            => env['SMTP_LOGIN'].presence,
+            :password             => env['SMTP_PASSWORD'].presence,
+            :domain               => env['LOCAL_DOMAIN'],
+            :authentication       => :plain,
+            :enable_starttls_auto => true,
+          }
+
+          ActionMailer::Base.default_options = {
+            from: env['SMTP_FROM_ADDRESS'],
+          }
+
+          mail = ActionMailer::Base.new.mail to: send_to, subject: 'Test', body: 'Mastodon SMTP configuration works!'
+          mail.deliver
+        rescue StandardError => e
+          prompt.error 'E-mail could not be sent with this configuration, try again.'
+          prompt.error e.message
+          break unless prompt.yes?('Try again?')
+        end
+      end
+
+      prompt.say "\n"
+      prompt.say 'This configuration will be written to .env.production'
+
+      if prompt.yes?('Save configuration?')
+        cmd = TTY::Command.new(printer: :quiet)
+
+        File.write(Rails.root.join('.env.production'), "# Generated with mastodon:setup on #{Time.now.utc}\n\n" + env.each_pair.map { |key, value| "#{key}=#{value}" }.join("\n") + "\n")
+
+        if using_docker
+          prompt.ok 'Below is your configuration, save it to an .env.production file outside Docker:'
+          prompt.say "\n"
+          prompt.say File.read(Rails.root.join('.env.production'))
+          prompt.say "\n"
+          prompt.ok 'It is also saved within this container so you can proceed with this wizard.'
+        end
+
+        prompt.say "\n"
+        prompt.say 'Now that configuration is saved, the database schema must be loaded.'
+        prompt.warn 'If the database already exists, this will erase its contents.'
+
+        if prompt.yes?('Prepare the database now?')
+          prompt.say 'Running `RAILS_ENV=production rails db:setup` ...'
+          prompt.say "\n"
+
+          if cmd.run!({ RAILS_ENV: 'production' }, :rails, 'db:setup').failure?
+            prompt.say "\n"
+            prompt.error 'That failed! Perhaps your configuration is not right'
+          else
+            prompt.say "\n"
+            prompt.ok 'Done!'
+          end
+        end
+
+        prompt.say "\n"
+        prompt.say 'The final step is compiling CSS/JS assets.'
+        prompt.say 'This may take a while and consume a lot of RAM.'
+
+        if prompt.yes?('Compile the assets now?')
+          prompt.say 'Running `RAILS_ENV=production rails assets:precompile` ...'
+          prompt.say "\n"
+
+          if cmd.run!({ RAILS_ENV: 'production' }, :rails, 'assets:precompile').failure?
+            prompt.say "\n"
+            prompt.error 'That failed! Maybe you need swap space?'
+          else
+            prompt.say "\n"
+            prompt.say 'Done!'
+          end
+        end
+
+        prompt.say "\n"
+        prompt.ok 'All done! You can now power on the Mastodon server 🐘'
+        prompt.say "\n"
+
+        if db_connection_works && prompt.yes?('Do you want to create an admin user straight away?')
+          env.each_pair do |key, value|
+            ENV[key] = value.to_s
+          end
+
+          require_relative '../../config/environment'
+          disable_log_stdout!
+
+          username = prompt.ask('Username:') do |q|
+            q.required true
+            q.default 'admin'
+            q.validate(/\A[a-z0-9_]+\z/i)
+            q.modify :strip
+          end
+
+          email = prompt.ask('E-mail:') do |q|
+            q.required true
+            q.modify :strip
+          end
+
+          password = SecureRandom.hex(16)
+
+          user = User.new(admin: true, email: email, password: password, confirmed_at: Time.now.utc, account_attributes: { username: username })
+          user.save(validate: false)
+
+          prompt.ok "You can login with the password: #{password}"
+          prompt.warn 'You can change your password once you login.'
+        end
+      else
+        prompt.warn 'Nothing saved. Bye!'
+      end
+    rescue TTY::Reader::InputInterrupt
+      prompt.ok 'Aborting. Bye!'
+    end
+  end
+
   desc 'Execute daily tasks (deprecated)'
   task :daily do
     # No-op
@@ -67,32 +431,40 @@ namespace :mastodon do
   desc 'Add a user by providing their email, username and initial password.' \
        'The user will receive a confirmation email, then they must reset their password before logging in.'
   task add_user: :environment do
-    print 'Enter email: '
-    email = STDIN.gets.chomp
-
-    print 'Enter username: '
-    username = STDIN.gets.chomp
-
-    print 'Create user and send them confirmation mail [y/N]: '
-    confirm = STDIN.gets.chomp
-    puts
-
-    if confirm.casecmp('y').zero?
-      password = SecureRandom.hex
-      user = User.new(email: email, password: password, account_attributes: { username: username })
-      if user.save
-        puts 'User added and confirmation mail sent to user\'s email address.'
-        puts "Here is the random password generated for the user: #{password}"
-      else
-        puts 'Following errors occured while creating new user:'
-        user.errors.each do |key, val|
-          puts "#{key}: #{val}"
+    disable_log_stdout!
+
+    prompt = TTY::Prompt.new
+
+    begin
+      email = prompt.ask('E-mail:', required: true) do |q|
+        q.modify :strip
+      end
+
+      username = prompt.ask('Username:', required: true) do |q|
+        q.modify :strip
+      end
+
+      role = prompt.select('Role:', %w(user moderator admin))
+
+      if prompt.yes?('Proceed to create the user?')
+        user = User.new(email: email, password: SecureRandom.hex, admin: role == 'admin', moderator: role == 'moderator', account_attributes: { username: username })
+
+        if user.save
+          prompt.ok 'User created and confirmation mail sent to the user\'s email address.'
+          prompt.ok "Here is the random password generated for the user: #{password}"
+        else
+          prompt.warn 'User was not created because of the following errors:'
+
+          user.errors.each do |key, val|
+            prompt.error "#{key}: #{val}"
+          end
         end
+      else
+        prompt.ok 'Aborting. Bye!'
       end
-    else
-      puts 'Aborted by user.'
+    rescue TTY::Reader::InputInterrupt
+      prompt.ok 'Aborting. Bye!'
     end
-    puts
   end
 
   namespace :media do
@@ -112,6 +484,8 @@ namespace :mastodon do
       time_ago = ENV.fetch('NUM_DAYS') { 7 }.to_i.days.ago
 
       MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).find_each do |media|
+        next unless media.file.exists?
+
         media.file.destroy
         media.save
       end
@@ -130,9 +504,13 @@ namespace :mastodon do
       accounts = accounts.where(domain: ENV['DOMAIN']) if ENV['DOMAIN'].present?
 
       accounts.find_each do |account|
-        account.reset_avatar!
-        account.reset_header!
-        account.save
+        begin
+          account.reset_avatar!
+          account.reset_header!
+          account.save
+        rescue Paperclip::Error
+          puts "Error resetting avatar and header for account #{username}@#{domain}"
+        end
       end
     end
   end
@@ -388,6 +766,7 @@ namespace :mastodon do
 
         if [404, 410].include?(res.code)
           if options[:force]
+            SuspendAccountService.new.call(account)
             account.destroy
           else
             progress_bar.pause
@@ -400,6 +779,7 @@ namespace :mastodon do
             if confirm.casecmp('n').zero?
               next
             else
+              SuspendAccountService.new.call(account)
               account.destroy
             end
           end
diff --git a/nanobox/nginx-web.conf.erb b/nanobox/nginx-web.conf.erb
index a839f3036..797201eab 100644
--- a/nanobox/nginx-web.conf.erb
+++ b/nanobox/nginx-web.conf.erb
@@ -58,15 +58,21 @@ http {
             proxy_pass_header Server;
 
             proxy_pass http://rails;
-            proxy_buffering off;
+            proxy_buffering on;
             proxy_redirect off;
             proxy_http_version 1.1;
             proxy_set_header Upgrade $http_upgrade;
             proxy_set_header Connection $connection_upgrade;
 
+            proxy_cache CACHE;
+            proxy_cache_valid 200 7d;
+            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
+
             tcp_nodelay on;
         }
     }
 
+    proxy_cache_path /data/var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;
+
     error_page 500 501 502 503 504 /500.html;
 }
diff --git a/package.json b/package.json
index 277e5bb63..d84bfb06f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "mastodon",
-  "license": "AGPL-3.0",
+  "license": "AGPL-3.0-or-later",
   "engines": {
     "node": ">=6"
   },
@@ -10,10 +10,9 @@
     "build:production": "cross-env RAILS_ENV=production ./bin/webpack",
     "manage:translations": "node ./config/webpack/translationRunner.js",
     "start": "node ./streaming/index.js",
-    "test": "npm run test:lint && npm run test:jest",
+    "test": "npm-run-all test:lint test:jest",
     "test:lint": "eslint -c .eslintrc.yml --ext=js app/javascript/ config/webpack/ streaming/",
-    "test:jest": "cross-env NODE_ENV=test jest --coverage",
-    "postinstall": "npm rebuild node-sass"
+    "test:jest": "cross-env NODE_ENV=test jest --coverage"
   },
   "repository": {
     "type": "git",
@@ -69,9 +68,11 @@
     "marky": "^1.2.0",
     "mkdirp": "^0.5.1",
     "node-sass": "^4.7.2",
+    "npm-run-all": "^4.1.2",
     "npmlog": "^4.1.2",
     "object-assign": "^4.1.1",
     "object-fit-images": "^3.2.3",
+    "object.values": "^1.0.4",
     "offline-plugin": "^4.8.3",
     "path-complete-extname": "^0.1.0",
     "pg": "^6.4.0",
diff --git a/spec/controllers/api/salmon_controller_spec.rb b/spec/controllers/api/salmon_controller_spec.rb
index 323d85b61..8af8b83a8 100644
--- a/spec/controllers/api/salmon_controller_spec.rb
+++ b/spec/controllers/api/salmon_controller_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Api::SalmonController, type: :controller do
       end
     end
 
-    context 'with invalid post data' do
+    context 'with empty post data' do
       before do
         request.env['RAW_POST_DATA'] = ''
         post :update, params: { id: account.id }
@@ -50,5 +50,19 @@ RSpec.describe Api::SalmonController, type: :controller do
         expect(response).to have_http_status(400)
       end
     end
+
+    context 'with invalid post data' do
+      before do
+        service = double(call: false)
+        allow(VerifySalmonService).to receive(:new).and_return(service)
+
+        request.env['RAW_POST_DATA'] = File.read(File.join(Rails.root, 'spec', 'fixtures', 'salmon', 'mention.xml'))
+        post :update, params: { id: account.id }
+      end
+
+      it 'returns http client error' do
+        expect(response).to have_http_status(401)
+      end
+    end
   end
 end
diff --git a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
index 508415fc8..e0de790c8 100644
--- a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
@@ -66,6 +66,28 @@ describe Api::V1::Accounts::RelationshipsController do
         expect(json.second[:requested]).to be false
         expect(json.second[:domain_blocking]).to be false
       end
+
+      it 'returns JSON with correct data on cached requests too' do
+        get :index, params: { id: [simon.id] }
+
+        json = body_as_json
+
+        expect(json).to be_a Enumerable
+        expect(json.first[:following]).to be true
+        expect(json.first[:showing_reblogs]).to be true
+      end
+
+      it 'returns JSON with correct data after change too' do
+        user.account.unfollow!(simon)
+
+        get :index, params: { id: [simon.id] }
+
+        json = body_as_json
+
+        expect(json).to be_a Enumerable
+        expect(json.first[:following]).to be false
+        expect(json.first[:showing_reblogs]).to be false
+      end
     end
   end
 end
diff --git a/spec/controllers/authorize_follows_controller_spec.rb b/spec/controllers/authorize_follows_controller_spec.rb
index 26e46a23c..b1cbef7ea 100644
--- a/spec/controllers/authorize_follows_controller_spec.rb
+++ b/spec/controllers/authorize_follows_controller_spec.rb
@@ -30,7 +30,7 @@ describe AuthorizeFollowsController do
 
       it 'renders error when account cant be found' do
         service = double
-        allow(ResolveRemoteAccountService).to receive(:new).and_return(service)
+        allow(ResolveAccountService).to receive(:new).and_return(service)
         allow(service).to receive(:call).with('missing@hostname').and_return(nil)
 
         get :show, params: { acct: 'acct:missing@hostname' }
@@ -54,7 +54,7 @@ describe AuthorizeFollowsController do
       it 'sets account from acct uri' do
         account = Account.new
         service = double
-        allow(ResolveRemoteAccountService).to receive(:new).and_return(service)
+        allow(ResolveAccountService).to receive(:new).and_return(service)
         allow(service).to receive(:call).with('found@hostname').and_return(account)
 
         get :show, params: { acct: 'acct:found@hostname' }
diff --git a/spec/controllers/concerns/localized_spec.rb b/spec/controllers/concerns/localized_spec.rb
index c917ce85e..f71c96aff 100644
--- a/spec/controllers/concerns/localized_spec.rb
+++ b/spec/controllers/concerns/localized_spec.rb
@@ -16,49 +16,24 @@ describe ApplicationController, type: :controller do
   end
 
   shared_examples 'default locale' do
-    context 'when DEFAULT_LOCALE environment variable is set' do
-      around do |example|
-        ClimateControl.modify 'DEFAULT_LOCALE' => 'ca', &example.method(:run)
-        I18n.locale = I18n.default_locale
-      end
+    after { I18n.locale = I18n.default_locale }
 
-      it 'sets language specified by ENV if preferred' do
-        request.headers['Accept-Language'] = 'ca, fa'
-        get 'success'
-        expect(I18n.locale).to eq :ca
-      end
-
-      it 'sets available and preferred language if language specified by ENV is not preferred' do
-        request.headers['Accept-Language'] = 'ca-ES, fa'
-        get 'success'
-        expect(I18n.locale).to eq :fa
-      end
-
-      it 'sets language specified by ENV if it is compatible and none of available languages are preferred' do
-        request.headers['Accept-Language'] = 'ca-ES, fa-IR'
-        get 'success'
-        expect(I18n.locale).to eq :ca
-      end
-
-      it 'sets available and compatible langauge if language specified by ENV is not compatible none of available languages are preferred' do
-        request.headers['Accept-Language'] = 'fa-IR'
-        get 'success'
-        expect(I18n.locale).to eq :fa
-      end
+    it 'sets available and preferred language' do
+      request.headers['Accept-Language'] = 'ca-ES, fa'
+      get 'success'
+      expect(I18n.locale).to eq :fa
+    end
 
-      it 'sets language specified by ENV if none of available languages are compatible' do
-        request.headers['Accept-Language'] = ''
-        get 'success'
-        expect(I18n.locale).to eq :ca
-      end
+    it 'sets available and compatible langauge if none of available languages are preferred' do
+      request.headers['Accept-Language'] = 'fa-IR'
+      get 'success'
+      expect(I18n.locale).to eq :fa
     end
 
-    context 'when DEFAULT_LOCALE environment variable is not set' do
-      it 'sets default locale if none of available languages are compatible' do
-        request.headers['Accept-Language'] = ''
-        get 'success'
-        expect(I18n.locale).to eq :en
-      end
+    it 'sets default locale if none of available languages are compatible' do
+      request.headers['Accept-Language'] = ''
+      get 'success'
+      expect(I18n.locale).to eq :en
     end
   end
 
diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb
index 1077a7288..f43cf0c27 100644
--- a/spec/controllers/home_controller_spec.rb
+++ b/spec/controllers/home_controller_spec.rb
@@ -4,21 +4,24 @@ RSpec.describe HomeController, type: :controller do
   render_views
 
   describe 'GET #index' do
+    subject { get :index }
+
     context 'when not signed in' do
+      context 'when requested path is tag timeline' do
+        before { @request.path = '/web/timelines/tag/name' }
+        it { is_expected.to redirect_to '/tags/name' }
+      end
+
       it 'redirects to about page' do
         @request.path = '/'
-        get :index
-        expect(response).to redirect_to(about_path)
+        is_expected.to redirect_to(about_path)
       end
     end
 
     context 'when signed in' do
       let(:user) { Fabricate(:user) }
 
-      subject do
-        sign_in(user)
-        get :index
-      end
+      before { sign_in(user) }
 
       it 'assigns @body_classes' do
         subject
diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb
index 4810be701..59b10e0da 100644
--- a/spec/controllers/settings/imports_controller_spec.rb
+++ b/spec/controllers/settings/imports_controller_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe Settings::ImportsController, type: :controller do
   describe 'POST #create' do
     it 'redirects to settings path with successful following import' do
       service = double(call: nil)
-      allow(ResolveRemoteAccountService).to receive(:new).and_return(service)
+      allow(ResolveAccountService).to receive(:new).and_return(service)
       post :create, params: {
         import: {
           type: 'following',
@@ -30,7 +30,7 @@ RSpec.describe Settings::ImportsController, type: :controller do
 
     it 'redirects to settings path with successful blocking import' do
       service = double(call: nil)
-      allow(ResolveRemoteAccountService).to receive(:new).and_return(service)
+      allow(ResolveAccountService).to receive(:new).and_return(service)
       post :create, params: {
         import: {
           type: 'blocking',
diff --git a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
index 0676d6161..aee82a3d8 100644
--- a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
+++ b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
@@ -6,6 +6,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
   render_views
 
   let(:user) { Fabricate(:user, email: 'local-part@domain', otp_secret: 'thisisasecretforthespecofnewview') }
+  let(:user_without_otp_secret) { Fabricate(:user, email: 'local-part@domain') }
 
   shared_examples 'renders :new' do
     it 'renders the new view' do
@@ -33,6 +34,12 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do
       get :new
       expect(response).to redirect_to('/auth/sign_in')
     end
+
+    it 'redirects if user do not have otp_secret' do
+      sign_in user_without_otp_secret, scope: :user
+      get :new
+      expect(response).to redirect_to('/settings/two_factor_authentication')
+    end
   end
 
   describe 'POST #create' do
diff --git a/spec/fabricators/backup_fabricator.rb b/spec/fabricators/backup_fabricator.rb
new file mode 100644
index 000000000..99a5bdcda
--- /dev/null
+++ b/spec/fabricators/backup_fabricator.rb
@@ -0,0 +1,3 @@
+Fabricator(:backup) do
+  user
+end
diff --git a/spec/fabricators/identity_fabricator.rb b/spec/fabricators/identity_fabricator.rb
new file mode 100644
index 000000000..bc832df9f
--- /dev/null
+++ b/spec/fabricators/identity_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:identity) do
+  user     nil
+  provider "MyString"
+  uid      "MyString"
+end
diff --git a/spec/fabricators/list_fabricator.rb b/spec/fabricators/list_fabricator.rb
index d249c2029..2a61b317b 100644
--- a/spec/fabricators/list_fabricator.rb
+++ b/spec/fabricators/list_fabricator.rb
@@ -1,4 +1,4 @@
 Fabricator(:list) do
-  account nil
+  account
   title   "MyString"
 end
diff --git a/spec/fixtures/requests/webfinger-hacker3.txt b/spec/fixtures/requests/webfinger-hacker3.txt
new file mode 100644
index 000000000..c59f518bd
--- /dev/null
+++ b/spec/fixtures/requests/webfinger-hacker3.txt
@@ -0,0 +1,11 @@
+HTTP/1.1 200 OK

+Server: nginx/1.6.2

+Date: Sun, 20 Mar 2016 11:13:16 GMT

+Content-Type: application/jrd+json

+Transfer-Encoding: chunked

+Connection: keep-alive

+Access-Control-Allow-Origin: *

+Vary: Accept-Encoding,Cookie

+Strict-Transport-Security: max-age=31536000; includeSubdomains;

+

+{"subject":"acct:localhost@kickass.zone","aliases":["https:\/\/kickass.zone\/user\/7477","https:\/\/kickass.zone\/gargron","https:\/\/kickass.zone\/index.php\/user\/7477","https:\/\/kickass.zone\/index.php\/gargron"],"links":[{"rel":"http:\/\/webfinger.net\/rel\/profile-page","type":"text\/html","href":"https:\/\/kickass.zone\/gargron"},{"rel":"http:\/\/gmpg.org\/xfn\/11","type":"text\/html","href":"https:\/\/kickass.zone\/gargron"},{"rel":"describedby","type":"application\/rdf+xml","href":"https:\/\/kickass.zone\/gargron\/foaf"},{"rel":"http:\/\/apinamespace.org\/atom","type":"application\/atomsvc+xml","href":"https:\/\/kickass.zone\/api\/statusnet\/app\/service\/gargron.xml"},{"rel":"http:\/\/apinamespace.org\/twitter","href":"https:\/\/kickass.zone\/api\/"},{"rel":"http:\/\/specs.openid.net\/auth\/2.0\/provider","href":"https:\/\/kickass.zone\/gargron"},{"rel":"http:\/\/schemas.google.com\/g\/2010#updates-from","type":"application\/atom+xml","href":"https:\/\/kickass.zone\/api\/statuses\/user_timeline\/7477.atom"},{"rel":"magic-public-key","href":"data:application\/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"},{"rel":"salmon","href":"https:\/\/kickass.zone\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-replies","href":"https:\/\/kickass.zone\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-mention","href":"https:\/\/kickass.zone\/main\/salmon\/user\/7477"},{"rel":"http:\/\/ostatus.org\/schema\/1.0\/subscribe","template":"https:\/\/kickass.zone\/main\/ostatussub?profile={uri}"}]}

diff --git a/spec/helpers/instance_helper_spec.rb b/spec/helpers/instance_helper_spec.rb
index bc5950d91..c2e26dbed 100644
--- a/spec/helpers/instance_helper_spec.rb
+++ b/spec/helpers/instance_helper_spec.rb
@@ -15,12 +15,6 @@ describe InstanceHelper do
 
       expect(helper.site_title).to eq 'New site title'
     end
-
-    it 'returns empty string when Setting.site_title is nil' do
-      Setting.site_title = nil
-
-      expect(helper.site_title).to eq 'cb6e6126.ngrok.io'
-    end
   end
 
   describe 'site_hostname' do
diff --git a/spec/lib/activitypub/activity/add_spec.rb b/spec/lib/activitypub/activity/add_spec.rb
new file mode 100644
index 000000000..3ebab4e37
--- /dev/null
+++ b/spec/lib/activitypub/activity/add_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Add do
+  let(:sender) { Fabricate(:account, featured_collection_url: 'https://example.com/featured') }
+  let(:status) { Fabricate(:status, account: sender) }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Add',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: ActivityPub::TagManager.instance.uri_for(status),
+      target: sender.featured_collection_url,
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    before do
+      subject.perform
+    end
+
+    it 'creates a pin' do
+      expect(sender.pinned?(status)).to be true
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb
index 51f54a398..62b9db8c2 100644
--- a/spec/lib/activitypub/activity/create_spec.rb
+++ b/spec/lib/activitypub/activity/create_spec.rb
@@ -202,7 +202,7 @@ RSpec.describe ActivityPub::Activity::Create do
           attachment: [
             {
               type: 'Document',
-              mime_type: 'image/png',
+              mediaType: 'image/png',
               url: 'http://example.com/attachment.png',
             },
           ],
@@ -217,6 +217,31 @@ RSpec.describe ActivityPub::Activity::Create do
       end
     end
 
+    context 'with media attachments with focal points' do
+      let(:object_json) do
+        {
+          id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
+          type: 'Note',
+          content: 'Lorem ipsum',
+          attachment: [
+            {
+              type: 'Document',
+              mediaType: 'image/png',
+              url: 'http://example.com/attachment.png',
+              focalPoint: [0.5, -0.7],
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.media_attachments.map(&:focus)).to include('0.5,-0.7')
+      end
+    end
+
     context 'with media attachments missing url' do
       let(:object_json) do
         {
@@ -226,7 +251,7 @@ RSpec.describe ActivityPub::Activity::Create do
           attachment: [
             {
               type: 'Document',
-              mime_type: 'image/png',
+              mediaType: 'image/png',
             },
           ],
         }
diff --git a/spec/lib/activitypub/activity/flag_spec.rb b/spec/lib/activitypub/activity/flag_spec.rb
new file mode 100644
index 000000000..3f082a813
--- /dev/null
+++ b/spec/lib/activitypub/activity/flag_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Flag do
+  let(:sender)  { Fabricate(:account, domain: 'example.com') }
+  let(:flagged) { Fabricate(:account) }
+  let(:status)  { Fabricate(:status, account: flagged, uri: 'foobar') }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: nil,
+      type: 'Flag',
+      content: 'Boo!!',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: [
+        ActivityPub::TagManager.instance.uri_for(flagged),
+        ActivityPub::TagManager.instance.uri_for(status),
+      ],
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    before do
+      subject.perform
+    end
+
+    it 'creates a report' do
+      report = Report.find_by(account: sender, target_account: flagged)
+
+      expect(report).to_not be_nil
+      expect(report.comment).to eq 'Boo!!'
+      expect(report.status_ids).to eq [status.id]
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/remove_spec.rb b/spec/lib/activitypub/activity/remove_spec.rb
new file mode 100644
index 000000000..4209dfde2
--- /dev/null
+++ b/spec/lib/activitypub/activity/remove_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Remove do
+  let(:sender) { Fabricate(:account, featured_collection_url: 'https://example.com/featured') }
+  let(:status) { Fabricate(:status, account: sender) }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Add',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: ActivityPub::TagManager.instance.uri_for(status),
+      target: sender.featured_collection_url,
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    before do
+      StatusPin.create!(account: sender, status: status)
+      subject.perform
+    end
+
+    it 'removes a pin' do
+      expect(sender.pinned?(status)).to be false
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb
index ea308e35c..fbfc585cf 100644
--- a/spec/lib/activitypub/activity/update_spec.rb
+++ b/spec/lib/activitypub/activity/update_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe ActivityPub::Activity::Update do
     stub_request(:get, actor_json[:outbox]).to_return(status: 404)
     stub_request(:get, actor_json[:followers]).to_return(status: 404)
     stub_request(:get, actor_json[:following]).to_return(status: 404)
+    stub_request(:get, actor_json[:featured]).to_return(status: 404)
 
     sender.update!(uri: ActivityPub::TagManager.instance.uri_for(sender))
   end
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index f87ef383a..3f91bba4e 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -1,7 +1,14 @@
 require 'rails_helper'
 
 RSpec.describe FeedManager do
-  it 'tracks at least as many statuses as reblogs' do
+  before do |example|
+    unless example.metadata[:skip_stub]
+      stub_const 'FeedManager::MAX_ITEMS', 10
+      stub_const 'FeedManager::REBLOG_FALLOFF', 4
+    end
+  end
+
+  it 'tracks at least as many statuses as reblogs', skip_stub: true do
     expect(FeedManager::REBLOG_FALLOFF).to be <= FeedManager::MAX_ITEMS
   end
 
@@ -218,7 +225,7 @@ RSpec.describe FeedManager do
     end
   end
 
-  describe '#push' do
+  describe '#push_to_home' do
     it 'trims timelines if they will have more than FeedManager::MAX_ITEMS' do
       account = Fabricate(:account)
       status = Fabricate(:status)
@@ -309,6 +316,39 @@ RSpec.describe FeedManager do
         expect(FeedManager.instance.push_to_home(account, reblogs.last)).to be true
       end
     end
+
+    it "does not push when the given status's reblog is already inserted" do
+      account = Fabricate(:account)
+      reblog = Fabricate(:status)
+      status = Fabricate(:status, reblog: reblog)
+      FeedManager.instance.push_to_home(account, status)
+
+      expect(FeedManager.instance.push_to_home(account, reblog)).to eq false
+    end
+  end
+
+  describe '#push_to_list' do
+    it "does not push when the given status's reblog is already inserted" do
+      list = Fabricate(:list)
+      reblog = Fabricate(:status)
+      status = Fabricate(:status, reblog: reblog)
+      FeedManager.instance.push_to_list(list, status)
+
+      expect(FeedManager.instance.push_to_list(list, reblog)).to eq false
+    end
+  end
+
+  describe '#merge_into_timeline' do
+    it "does not push source account's statuses whose reblogs are already inserted" do
+      account = Fabricate(:account, id: 0)
+      reblog = Fabricate(:status)
+      status = Fabricate(:status, reblog: reblog)
+      FeedManager.instance.push_to_home(account, status)
+
+      FeedManager.instance.merge_into_timeline(account, reblog.account)
+
+      expect(Redis.current.zscore("feed:home:0", reblog.id)).to eq nil
+    end
   end
 
   describe '#trim' do
diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb
index 782f14b18..dc7daa52c 100644
--- a/spec/lib/request_spec.rb
+++ b/spec/lib/request_spec.rb
@@ -38,17 +38,32 @@ describe Request do
   end
 
   describe '#perform' do
-    before do
-      stub_request(:get, 'http://example.com')
-      subject.perform
-    end
+    context 'with valid host' do
+      before do
+        stub_request(:get, 'http://example.com')
+        subject.perform
+      end
+
+      it 'executes a HTTP request' do
+        expect(a_request(:get, 'http://example.com')).to have_been_made.once
+      end
 
-    it 'executes a HTTP request' do
-      expect(a_request(:get, 'http://example.com')).to have_been_made.once
+      it 'sets headers' do
+        expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
+      end
     end
 
-    it 'sets headers' do
-      expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
+    context 'with private host' do
+      around do |example|
+        WebMock.disable!
+        example.run
+        WebMock.enable!
+      end
+
+      it 'raises Mastodon::ValidationError' do
+        allow(IPSocket).to receive(:getaddress).with('example.com').and_return('0.0.0.0')
+        expect{ subject.perform }.to raise_error Mastodon::ValidationError
+      end
     end
   end
 end
diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb
index 811435606..1be01e8ba 100644
--- a/spec/mailers/notification_mailer_spec.rb
+++ b/spec/mailers/notification_mailer_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe NotificationMailer, type: :mailer do
 
   shared_examples 'localized subject' do |*args, **kwrest|
     it 'renders subject localized for the locale of the receiver' do
-      locale = I18n.available_locales.sample
+      locale = %i(de en).sample
       receiver.update!(locale: locale)
       expect(mail.subject).to eq I18n.t(*args, kwrest.merge(locale: locale))
     end
diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb
index 8d2a9368d..d9cdb9264 100644
--- a/spec/mailers/previews/user_mailer_preview.rb
+++ b/spec/mailers/previews/user_mailer_preview.rb
@@ -34,4 +34,9 @@ class UserMailerPreview < ActionMailer::Preview
   def welcome
     UserMailer.welcome(User.first)
   end
+
+  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/backup_ready
+  def backup_ready
+    UserMailer.backup_ready(User.first, Backup.first)
+  end
 end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 7501c498c..3ac7208ed 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -185,8 +185,8 @@ RSpec.describe Account, type: :model do
         expect(account.refresh!).to be_nil
       end
 
-      it 'calls not ResolveRemoteAccountService#call' do
-        expect_any_instance_of(ResolveRemoteAccountService).not_to receive(:call).with(acct)
+      it 'calls not ResolveAccountService#call' do
+        expect_any_instance_of(ResolveAccountService).not_to receive(:call).with(acct)
         account.refresh!
       end
     end
@@ -194,8 +194,8 @@ RSpec.describe Account, type: :model do
     context 'domain is present' do
       let(:domain) { 'example.com' }
 
-      it 'calls ResolveRemoteAccountService#call' do
-        expect_any_instance_of(ResolveRemoteAccountService).to receive(:call).with(acct).once
+      it 'calls ResolveAccountService#call' do
+        expect_any_instance_of(ResolveAccountService).to receive(:call).with(acct).once
         account.refresh!
       end
     end
diff --git a/spec/models/backup_spec.rb b/spec/models/backup_spec.rb
new file mode 100644
index 000000000..fabcdc845
--- /dev/null
+++ b/spec/models/backup_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Backup, type: :model do
+
+end
diff --git a/spec/models/glitch/keyword_mute_helper_spec.rb b/spec/models/glitch/keyword_mute_helper_spec.rb
new file mode 100644
index 000000000..b3f991d5b
--- /dev/null
+++ b/spec/models/glitch/keyword_mute_helper_spec.rb
@@ -0,0 +1,50 @@
+require 'rails_helper'
+
+RSpec.describe Glitch::KeywordMuteHelper do
+  describe '#matches?' do
+    let(:alice) { Fabricate(:account, username: 'alice').tap(&:save!) }
+    let(:helper) { Glitch::KeywordMuteHelper.new(alice) }
+
+    it 'ignores names of HTML tags in status text' do
+      status = Fabricate(:status, text: '<addr>uh example</addr>')
+      Glitch::KeywordMute.create!(account: alice, keyword: 'addr')
+
+      expect(helper.matches?(status)).to be false
+    end
+
+    it 'ignores properties of HTML tags in status text' do
+      status = Fabricate(:status, text: '<a href="https://www.example.org">uh example</a>')
+      Glitch::KeywordMute.create!(account: alice, keyword: 'href')
+
+      expect(helper.matches?(status)).to be false
+    end
+
+    it 'matches text inside HTML tags' do
+      status = Fabricate(:status, text: '<p>HEY THIS IS SOMETHING ANNOYING</p>')
+      Glitch::KeywordMute.create!(account: alice, keyword: 'annoying')
+
+      expect(helper.matches?(status)).to be true
+    end
+
+    it 'matches < in HTML-stripped text' do
+      status = Fabricate(:status, text: '<p>I <3 oats</p>')
+      Glitch::KeywordMute.create!(account: alice, keyword: '<3')
+
+      expect(helper.matches?(status)).to be true
+    end
+
+    it 'matches &lt; in HTML text' do
+      status = Fabricate(:status, text: '<p>I &lt;3 oats</p>')
+      Glitch::KeywordMute.create!(account: alice, keyword: '<3')
+
+      expect(helper.matches?(status)).to be true
+    end
+
+    it 'matches link hrefs in HTML text' do
+      status = Fabricate(:status, text: '<p><a href="https://example.com/it-was-milk">yep</a></p>')
+      Glitch::KeywordMute.create!(account: alice, keyword: 'milk')
+
+      expect(helper.matches?(status)).to be true
+    end
+  end
+end
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
new file mode 100644
index 000000000..53f355410
--- /dev/null
+++ b/spec/models/identity_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Identity, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb
index b40a641f7..d2230b6d0 100644
--- a/spec/models/media_attachment_spec.rb
+++ b/spec/models/media_attachment_spec.rb
@@ -92,7 +92,6 @@ RSpec.describe MediaAttachment, type: :model do
     it 'sets meta' do
       expect(media.file.meta["original"]["width"]).to eq 128
       expect(media.file.meta["original"]["height"]).to eq 128
-      expect(media.file.meta["original"]["aspect"]).to eq 1.0
     end
   end
 
diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb
index e99dfc0d7..1cc528674 100644
--- a/spec/models/setting_spec.rb
+++ b/spec/models/setting_spec.rb
@@ -42,17 +42,12 @@ RSpec.describe Setting, type: :model do
         described_class[key]
       end
 
-      it 'calls Rails.cache.fetch' do
-        expect(Rails).to receive_message_chain(:cache, :fetch).with(cache_key)
-        described_class[key]
-      end
-
       context 'Rails.cache does not exists' do
         before do
           allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object)
           allow(described_class).to receive(:default_settings).and_return(default_settings)
           allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records)
-          Rails.cache.clear(cache_key)
+          Rails.cache.delete(cache_key)
         end
 
         let(:object)           { nil }
@@ -103,6 +98,14 @@ RSpec.describe Setting, type: :model do
           Rails.cache.write(cache_key, cache_value)
         end
 
+        it 'does not query the database' do
+          expect do |callback|
+            ActiveSupport::Notifications.subscribed callback, 'sql.active_record' do
+              described_class[key]
+            end
+          end.not_to yield_control
+        end
+
         it 'returns the cached value' do
           expect(described_class[key]).to eq cache_value
         end
diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb
index 7c47aed61..006403925 100644
--- a/spec/presenters/instance_presenter_spec.rb
+++ b/spec/presenters/instance_presenter_spec.rb
@@ -90,9 +90,7 @@ describe InstancePresenter do
 
   describe "user_count" do
     it "returns the number of site users" do
-      cache = double
-      allow(Rails).to receive(:cache).and_return(cache)
-      allow(cache).to receive(:fetch).with("user_count").and_return(123)
+      Rails.cache.write 'user_count', 123
 
       expect(instance_presenter.user_count).to eq(123)
     end
@@ -100,9 +98,7 @@ describe InstancePresenter do
 
   describe "status_count" do
     it "returns the number of local statuses" do
-      cache = double
-      allow(Rails).to receive(:cache).and_return(cache)
-      allow(cache).to receive(:fetch).with("local_status_count").and_return(234)
+      Rails.cache.write 'local_status_count', 234
 
       expect(instance_presenter.status_count).to eq(234)
     end
@@ -110,9 +106,7 @@ describe InstancePresenter do
 
   describe "domain_count" do
     it "returns the number of known domains" do
-      cache = double
-      allow(Rails).to receive(:cache).and_return(cache)
-      allow(cache).to receive(:fetch).with("distinct_domain_count").and_return(345)
+      Rails.cache.write 'distinct_domain_count', 345
 
       expect(instance_presenter.domain_count).to eq(345)
     end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 4f7399505..dc1f32e08 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -51,6 +51,8 @@ RSpec.configure do |config|
   end
 
   config.after :each do
+    Rails.cache.clear
+
     keys = Redis.current.keys
     Redis.current.del(keys) if keys.any?
   end
diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb
index 4685e619f..9bb27edad 100644
--- a/spec/services/account_search_service_spec.rb
+++ b/spec/services/account_search_service_spec.rb
@@ -123,7 +123,7 @@ describe AccountSearchService do
     describe 'when there is a domain but no exact match' do
       it 'follows the remote account when resolve is true' do
         service = double(call: nil)
-        allow(ResolveRemoteAccountService).to receive(:new).and_return(service)
+        allow(ResolveAccountService).to receive(:new).and_return(service)
 
         results = subject.call('newuser@remote.com', 10, nil, resolve: true)
         expect(service).to have_received(:call).with('newuser@remote.com')
@@ -131,7 +131,7 @@ describe AccountSearchService do
 
       it 'does not follow the remote account when resolve is false' do
         service = double(call: nil)
-        allow(ResolveRemoteAccountService).to receive(:new).and_return(service)
+        allow(ResolveAccountService).to receive(:new).and_return(service)
 
         results = subject.call('newuser@remote.com', 10, nil, resolve: false)
         expect(service).not_to have_received(:call)
diff --git a/spec/services/fetch_atom_service_spec.rb b/spec/services/fetch_atom_service_spec.rb
index 5491fd027..2bd127e92 100644
--- a/spec/services/fetch_atom_service_spec.rb
+++ b/spec/services/fetch_atom_service_spec.rb
@@ -1,4 +1,89 @@
 require 'rails_helper'
 
 RSpec.describe FetchAtomService do
+  describe '#call' do
+    let(:url) { 'http://example.com' }
+    subject { FetchAtomService.new.call(url) }
+
+    context 'url is blank' do
+      let(:url) { '' }
+      it { is_expected.to be_nil }
+    end
+
+    context 'request failed' do
+      before do
+        WebMock.stub_request(:get, url).to_return(status: 500, body: '', headers: {})
+      end
+
+      it { is_expected.to be_nil }
+    end
+
+    context 'raise OpenSSL::SSL::SSLError' do
+      before do
+        allow(Request).to receive_message_chain(:new, :add_headers, :perform).and_raise(OpenSSL::SSL::SSLError)
+      end
+
+      it 'output log and return nil' do
+        expect_any_instance_of(ActiveSupport::Logger).to receive(:debug).with('SSL error: OpenSSL::SSL::SSLError')
+        is_expected.to be_nil
+      end
+    end
+
+    context 'raise HTTP::ConnectionError' do
+      before do
+        allow(Request).to receive_message_chain(:new, :add_headers, :perform).and_raise(HTTP::ConnectionError)
+      end
+
+      it 'output log and return nil' do
+        expect_any_instance_of(ActiveSupport::Logger).to receive(:debug).with('HTTP ConnectionError: HTTP::ConnectionError')
+        is_expected.to be_nil
+      end
+    end
+
+    context 'response success' do
+      let(:body) { '' }
+      let(:headers) { { 'Content-Type' => content_type } }
+      let(:json) {
+        { id: 1,
+          '@context': ActivityPub::TagManager::CONTEXT,
+          type: 'Note',
+        }.to_json
+      }
+
+      before do
+        WebMock.stub_request(:get, url).to_return(status: 200, body: body, headers: headers)
+      end
+
+      context 'content type is application/atom+xml' do
+        let(:content_type) { 'application/atom+xml' }
+
+        it { is_expected.to eq [url, {:prefetched_body=>""}, :ostatus] }
+      end
+
+      context 'content_type is json' do
+        let(:content_type) { 'application/activity+json' }
+        let(:body) { json }
+
+        it { is_expected.to eq [1, { prefetched_body: body, id: true }, :activitypub] }
+      end
+
+      before do
+        WebMock.stub_request(:get, url).to_return(status: 200, body: body, headers: headers)
+        WebMock.stub_request(:get, 'http://example.com/foo').to_return(status: 200, body: json, headers: { 'Content-Type' => 'application/activity+json' })
+      end
+
+      context 'has link header' do
+        let(:headers) { { 'Link' => '<http://example.com/foo>; rel="alternate"; type="application/activity+json"', } }
+
+        it { is_expected.to eq [1, { prefetched_body: json, id: true }, :activitypub] }
+      end
+
+      context 'content type is text/html' do
+        let(:content_type) { 'text/html' }
+        let(:body) { '<html><head><link rel="alternate" href="http://example.com/foo" type="application/activity+json"/></head></html>' }
+
+        it { is_expected.to eq [1, { prefetched_body: json, id: true }, :activitypub] }
+      end
+    end
+  end
 end
diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb
index ba61d22c3..edacc4425 100644
--- a/spec/services/fetch_link_card_service_spec.rb
+++ b/spec/services/fetch_link_card_service_spec.rb
@@ -15,6 +15,8 @@ RSpec.describe FetchLinkCardService do
     stub_request(:head, 'http://example.com/日本語').to_return(status: 200, headers: { 'Content-Type' => 'text/html' })
     stub_request(:get, 'http://example.com/日本語').to_return(request_fixture('sjis.txt'))
     stub_request(:head, 'https://github.com/qbi/WannaCry').to_return(status: 404)
+    stub_request(:head, 'http://example.com/test-').to_return(status: 200, headers: { 'Content-Type' => 'text/html' })
+    stub_request(:get, 'http://example.com/test-').to_return(request_fixture('idn.txt'))
 
     subject.call(status)
   end
@@ -63,6 +65,14 @@ RSpec.describe FetchLinkCardService do
         expect(status.preview_cards.first.title).to eq("SJISのページ")
       end
     end
+
+    context do
+      let(:status) { Fabricate(:status, text: 'test http://example.com/test-') }
+
+      it 'works with a URL ending with a hyphen' do
+        expect(a_request(:get, 'http://example.com/test-')).to have_been_made.at_least_once
+      end
+    end
   end
 
   context 'in a remote status' do
diff --git a/spec/services/fetch_remote_account_service_spec.rb b/spec/services/fetch_remote_account_service_spec.rb
index bb1877c7a..4388d4cf4 100644
--- a/spec/services/fetch_remote_account_service_spec.rb
+++ b/spec/services/fetch_remote_account_service_spec.rb
@@ -1,4 +1,71 @@
 require 'rails_helper'
 
 RSpec.describe FetchRemoteAccountService do
+  let(:url) { 'https://example.com' }
+  let(:prefetched_body) { nil }
+  let(:protocol) { :ostatus }
+  subject { FetchRemoteAccountService.new.call(url, prefetched_body, protocol) }
+
+  let(:actor) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'https://example.com/alice',
+      type: 'Person',
+      preferredUsername: 'alice',
+      name: 'Alice',
+      summary: 'Foo bar',
+      inbox: 'http://example.com/alice/inbox',
+    }
+  end
+
+  let(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
+  let(:xml) { File.read(File.join(Rails.root, 'spec', 'fixtures', 'xml', 'mastodon.atom')) }
+
+  shared_examples 'return Account' do
+    it { is_expected.to be_an Account }
+  end
+
+  context 'protocol is :activitypub' do
+    let(:prefetched_body) { Oj.dump(actor) }
+    let(:protocol) { :activitypub }
+
+    before do
+      stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
+    end
+
+    include_examples 'return Account'
+  end
+
+  context 'protocol is :ostatus' do
+    let(:prefetched_body) { xml }
+    let(:protocol) { :ostatus }
+
+    before do
+      stub_request(:get, "https://kickass.zone/.well-known/webfinger?resource=acct:localhost@kickass.zone").to_return(request_fixture('webfinger-hacker3.txt'))
+      stub_request(:get, "https://kickass.zone/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt'))
+    end
+
+    include_examples 'return Account'
+  end
+
+  context 'when prefetched_body is nil' do
+    context 'protocol is :activitypub' do
+      before do
+        stub_request(:get, url).to_return(status: 200, body: Oj.dump(actor), headers: { 'Content-Type' => 'application/activity+json' })
+        stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
+      end
+
+      include_examples 'return Account'
+    end
+
+    context 'protocol is :ostatus' do
+      before do
+        stub_request(:get, url).to_return(status: 200, body: xml, headers: { 'Content-Type' => 'application/atom+xml' })
+        stub_request(:get, "https://kickass.zone/.well-known/webfinger?resource=acct:localhost@kickass.zone").to_return(request_fixture('webfinger-hacker3.txt'))
+        stub_request(:get, "https://kickass.zone/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt'))
+      end
+
+      include_examples 'return Account'
+    end
+  end
 end
diff --git a/spec/services/fetch_remote_status_service_spec.rb b/spec/services/fetch_remote_status_service_spec.rb
index cbdecbf25..fa5782b94 100644
--- a/spec/services/fetch_remote_status_service_spec.rb
+++ b/spec/services/fetch_remote_status_service_spec.rb
@@ -1,4 +1,35 @@
 require 'rails_helper'
 
 RSpec.describe FetchRemoteStatusService do
+  let(:account) { Fabricate(:account) }
+  let(:prefetched_body) { nil }
+  let(:valid_domain) { Rails.configuration.x.local_domain }
+
+  let(:note) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: "https://#{valid_domain}/@foo/1234",
+      type: 'Note',
+      content: 'Lorem ipsum',
+      attributedTo: ActivityPub::TagManager.instance.uri_for(account),
+    }
+  end
+
+  context 'protocol is :activitypub' do
+    subject { described_class.new.call(note[:id], prefetched_body, protocol) }
+    let(:prefetched_body) { Oj.dump(note) }
+    let(:protocol) { :activitypub }
+
+    before do
+      account.update(uri: ActivityPub::TagManager.instance.uri_for(account))
+      subject
+    end
+
+    it 'creates status' do
+      status = account.statuses.first
+
+      expect(status).to_not be_nil
+      expect(status.text).to eq 'Lorem ipsum'
+    end
+  end
 end
diff --git a/spec/services/precompute_feed_service_spec.rb b/spec/services/precompute_feed_service_spec.rb
index 396a3c3fb..43340bffc 100644
--- a/spec/services/precompute_feed_service_spec.rb
+++ b/spec/services/precompute_feed_service_spec.rb
@@ -9,14 +9,11 @@ RSpec.describe PrecomputeFeedService do
     let(:account) { Fabricate(:account) }
     it 'fills a user timeline with statuses' do
       account = Fabricate(:account)
-      followed_account = Fabricate(:account)
-      Fabricate(:follow, account: account, target_account: followed_account)
-      reblog = Fabricate(:status, account: followed_account)
-      status = Fabricate(:status, account: account, reblog: reblog)
+      status = Fabricate(:status, account: account)
 
       subject.call(account)
 
-      expect(Redis.current.zscore(FeedManager.instance.key(:home, account.id), reblog.id)).to be_within(0.1).of(status.id.to_f)
+      expect(Redis.current.zscore(FeedManager.instance.key(:home, account.id), status.id)).to be_within(0.1).of(status.id.to_f)
     end
 
     it 'does not raise an error even if it could not find any status' do
diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb
new file mode 100644
index 000000000..2f926ef00
--- /dev/null
+++ b/spec/services/report_service_spec.rb
@@ -0,0 +1,25 @@
+require 'rails_helper'
+
+RSpec.describe ReportService do
+  subject { described_class.new }
+
+  let(:source_account) { Fabricate(:account) }
+
+  context 'for a remote account' do
+    let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
+
+    before do
+      stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
+    end
+
+    it 'sends ActivityPub payload when forward is true' do
+      subject.call(source_account, remote_account, forward: true)
+      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made
+    end
+
+    it 'does not send anything when forward is false' do
+      subject.call(source_account, remote_account, forward: false)
+      expect(a_request(:post, 'http://example.com/inbox')).to_not have_been_made
+    end
+  end
+end
diff --git a/spec/services/resolve_remote_account_service_spec.rb b/spec/services/resolve_account_service_spec.rb
index d0bb6a137..5f1b4467b 100644
--- a/spec/services/resolve_remote_account_service_spec.rb
+++ b/spec/services/resolve_account_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ResolveRemoteAccountService do
+RSpec.describe ResolveAccountService do
   subject { described_class.new }
 
   before do
diff --git a/spec/services/fetch_remote_resource_service_spec.rb b/spec/services/resolve_url_service_spec.rb
index b80fb2475..1e9be4c07 100644
--- a/spec/services/fetch_remote_resource_service_spec.rb
+++ b/spec/services/resolve_url_service_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-describe FetchRemoteResourceService do
+describe ResolveURLService do
   subject { described_class.new }
 
   describe '#call' do
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 3ffcc389b..957b60c7d 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -26,7 +26,7 @@ describe SearchService do
       context 'that does not find anything' do
         it 'returns the empty results' do
           service = double(call: nil)
-          allow(FetchRemoteResourceService).to receive(:new).and_return(service)
+          allow(ResolveURLService).to receive(:new).and_return(service)
           results = subject.call(@query, 10)
 
           expect(service).to have_received(:call).with(@query)
@@ -38,7 +38,7 @@ describe SearchService do
         it 'includes the account in the results' do
           account = Account.new
           service = double(call: account)
-          allow(FetchRemoteResourceService).to receive(:new).and_return(service)
+          allow(ResolveURLService).to receive(:new).and_return(service)
 
           results = subject.call(@query, 10)
           expect(service).to have_received(:call).with(@query)
@@ -50,7 +50,7 @@ describe SearchService do
         it 'includes the status in the results' do
           status = Status.new
           service = double(call: status)
-          allow(FetchRemoteResourceService).to receive(:new).and_return(service)
+          allow(ResolveURLService).to receive(:new).and_return(service)
 
           results = subject.call(@query, 10)
           expect(service).to have_received(:call).with(@query)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index eecaec4ac..a0466dd4b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -25,6 +25,10 @@ RSpec.configure do |config|
     end
   end
 
+  config.before :suite do
+    Chewy.strategy(:bypass)
+  end
+
   config.after :suite do
     gc_counter = 0
     FileUtils.rm_rf(Dir["#{Rails.root}/spec/test_files/"])
diff --git a/spec/views/about/show.html.haml_spec.rb b/spec/views/about/show.html.haml_spec.rb
index ca59fa9e3..be1320ed3 100644
--- a/spec/views/about/show.html.haml_spec.rb
+++ b/spec/views/about/show.html.haml_spec.rb
@@ -18,6 +18,9 @@ describe 'about/show.html.haml', without_verify_partial_doubles: true do
                                 source_url: 'https://github.com/tootsuite/mastodon',
                                 open_registrations: false,
                                 thumbnail: nil,
+                                hero: nil,
+                                user_count: 0,
+                                status_count: 0,
                                 closed_registrations_message: 'yes',
                                 commit_hash: commit_hash)
 
diff --git a/yarn.lock b/yarn.lock
index cc888a7b9..c97015fd8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -244,6 +244,10 @@ array-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
 
+array-filter@~0.0.0:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
+
 array-find-index@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
@@ -263,6 +267,14 @@ array-includes@^3.0.3:
     define-properties "^1.1.2"
     es-abstract "^1.7.0"
 
+array-map@~0.0.0:
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
+
+array-reduce@~0.0.0:
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
+
 array-union@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
@@ -2170,7 +2182,7 @@ double-ended-queue@^2.1.0-0:
   version "2.1.0-0"
   resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
 
-duplexer@^0.1.1:
+duplexer@^0.1.1, duplexer@~0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
 
@@ -2286,7 +2298,7 @@ error-ex@^1.2.0, error-ex@^1.3.1:
   dependencies:
     is-arrayish "^0.2.1"
 
-es-abstract@^1.6.1, es-abstract@^1.7.0:
+es-abstract@^1.4.3, es-abstract@^1.6.1, es-abstract@^1.7.0:
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864"
   dependencies:
@@ -2538,6 +2550,18 @@ event-emitter@~0.3.5:
     d "1"
     es5-ext "~0.10.14"
 
+event-stream@~3.3.0:
+  version "3.3.4"
+  resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571"
+  dependencies:
+    duplexer "~0.1.1"
+    from "~0"
+    map-stream "~0.1.0"
+    pause-stream "0.0.11"
+    split "0.3"
+    stream-combiner "~0.0.4"
+    through "~2.3.1"
+
 eventemitter3@1.x.x:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
@@ -2872,6 +2896,10 @@ fresh@0.5.2:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
 
+from@~0:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
+
 fs-extra@^0.30.0:
   version "0.30.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
@@ -4052,6 +4080,10 @@ json-loader@^0.5.4:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
 
+json-parse-better-errors@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz#50183cd1b2d25275de069e9e71b467ac9eab973a"
+
 json-schema-traverse@^0.3.0:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
@@ -4191,6 +4223,15 @@ load-json-file@^2.0.0:
     pify "^2.0.0"
     strip-bom "^3.0.0"
 
+load-json-file@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
+  dependencies:
+    graceful-fs "^4.1.2"
+    parse-json "^4.0.0"
+    pify "^3.0.0"
+    strip-bom "^3.0.0"
+
 loader-runner@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
@@ -4381,6 +4422,10 @@ map-obj@^1.0.0, map-obj@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
 
+map-stream@~0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194"
+
 mark-loader@^0.1.6:
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/mark-loader/-/mark-loader-0.1.6.tgz#0abb477dca7421d70e20128ff6489f5cae8676d5"
@@ -4429,6 +4474,10 @@ memory-fs@^0.4.0, memory-fs@~0.4.1:
     errno "^0.1.3"
     readable-stream "^2.0.1"
 
+memorystream@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
+
 meow@^3.3.0, meow@^3.7.0:
   version "3.7.0"
   resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
@@ -4760,6 +4809,20 @@ normalize-url@^1.4.0:
     query-string "^4.1.0"
     sort-keys "^1.0.0"
 
+npm-run-all@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.2.tgz#90d62d078792d20669139e718621186656cea056"
+  dependencies:
+    ansi-styles "^3.2.0"
+    chalk "^2.1.0"
+    cross-spawn "^5.1.0"
+    memorystream "^0.3.1"
+    minimatch "^3.0.4"
+    ps-tree "^1.1.0"
+    read-pkg "^3.0.0"
+    shell-quote "^1.6.1"
+    string.prototype.padend "^3.0.0"
+
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -5032,6 +5095,13 @@ parse-json@^3.0.0:
   dependencies:
     error-ex "^1.3.1"
 
+parse-json@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
+  dependencies:
+    error-ex "^1.3.1"
+    json-parse-better-errors "^1.0.1"
+
 parse5@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94"
@@ -5104,6 +5174,18 @@ path-type@^2.0.0:
   dependencies:
     pify "^2.0.0"
 
+path-type@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
+  dependencies:
+    pify "^3.0.0"
+
+pause-stream@0.0.11:
+  version "0.0.11"
+  resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
+  dependencies:
+    through "~2.3"
+
 pbkdf2@^3.0.3:
   version "3.0.14"
   resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade"
@@ -5767,6 +5849,12 @@ prr@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
 
+ps-tree@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014"
+  dependencies:
+    event-stream "~3.3.0"
+
 pseudomap@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
@@ -6130,6 +6218,14 @@ read-pkg@^2.0.0:
     normalize-package-data "^2.3.2"
     path-type "^2.0.0"
 
+read-pkg@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
+  dependencies:
+    load-json-file "^4.0.0"
+    normalize-package-data "^2.3.2"
+    path-type "^3.0.0"
+
 readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.2.9, readable-stream@^2.3.3:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
@@ -6679,6 +6775,15 @@ shebang-regex@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
 
+shell-quote@^1.6.1:
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767"
+  dependencies:
+    array-filter "~0.0.0"
+    array-map "~0.0.0"
+    array-reduce "~0.0.0"
+    jsonify "~0.0.0"
+
 shellwords@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
@@ -6813,6 +6918,12 @@ spdy@^3.4.1:
     select-hose "^2.0.0"
     spdy-transport "^2.0.18"
 
+split@0.3:
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f"
+  dependencies:
+    through "2"
+
 split@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9"
@@ -6858,6 +6969,12 @@ stream-browserify@^2.0.1:
     inherits "~2.0.1"
     readable-stream "^2.0.2"
 
+stream-combiner@~0.0.4:
+  version "0.0.4"
+  resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14"
+  dependencies:
+    duplexer "~0.1.1"
+
 stream-http@^2.7.2:
   version "2.7.2"
   resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad"
@@ -6894,6 +7011,14 @@ string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
 
+string.prototype.padend@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0"
+  dependencies:
+    define-properties "^1.1.2"
+    es-abstract "^1.4.3"
+    function-bind "^1.0.2"
+
 string_decoder@^1.0.0, string_decoder@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
@@ -7061,7 +7186,7 @@ throng@^4.0.0:
   dependencies:
     lodash.defaults "^4.0.1"
 
-through@2, through@^2.3.6:
+through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"