about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/logo.pngbin20674 -> 7752 bytes
-rw-r--r--app/assets/images/logo.svg2
-rw-r--r--app/assets/javascripts/components/actions/compose.jsx6
-rw-r--r--app/assets/javascripts/components/containers/mastodon.jsx2
-rw-r--r--app/assets/javascripts/components/features/compose/components/character_counter.jsx13
-rw-r--r--app/assets/javascripts/components/locales/fr.jsx22
-rw-r--r--app/assets/javascripts/components/locales/hr.jsx124
-rw-r--r--app/assets/javascripts/components/locales/index.jsx4
-rw-r--r--app/assets/javascripts/components/locales/nl.jsx44
-rw-r--r--app/assets/javascripts/components/locales/no.jsx2
-rw-r--r--app/assets/javascripts/components/locales/pt-br.jsx125
-rw-r--r--app/assets/javascripts/components/locales/pt.jsx191
-rw-r--r--app/assets/javascripts/components/locales/zh-cn.jsx157
-rw-r--r--app/assets/stylesheets/admin.scss2
-rw-r--r--app/assets/stylesheets/components.scss5
-rw-r--r--app/assets/stylesheets/forms.scss18
-rw-r--r--app/controllers/api/v1/statuses_controller.rb8
-rw-r--r--app/controllers/api_controller.rb13
-rw-r--r--app/controllers/auth/registrations_controller.rb1
-rw-r--r--app/controllers/media_controller.rb13
-rw-r--r--app/controllers/well_known/webfinger_controller.rb9
-rw-r--r--app/helpers/instance_helper.rb11
-rw-r--r--app/helpers/settings_helper.rb5
-rw-r--r--app/helpers/site_title_helper.rb7
-rw-r--r--app/lib/atom_serializer.rb6
-rw-r--r--app/mailers/application_mailer.rb1
-rw-r--r--app/mailers/user_mailer.rb2
-rw-r--r--app/models/account.rb14
-rw-r--r--app/models/block.rb10
-rw-r--r--app/models/favourite.rb6
-rw-r--r--app/models/media_attachment.rb1
-rw-r--r--app/models/mute.rb14
-rw-r--r--app/models/status.rb5
-rw-r--r--app/models/subscription.rb2
-rw-r--r--app/models/user.rb4
-rw-r--r--app/services/account_search_service.rb2
-rw-r--r--app/services/post_status_service.rb2
-rw-r--r--app/views/about/more.html.haml4
-rw-r--r--app/views/about/show.html.haml8
-rw-r--r--app/views/about/terms.en.html.haml2
-rw-r--r--app/views/about/terms.no.html.haml2
-rw-r--r--app/views/accounts/show.html.haml4
-rw-r--r--app/views/api/oembed/show.json.rabl2
-rw-r--r--app/views/api/v1/instances/show.rabl2
-rw-r--r--app/views/api/v1/statuses/_media.rabl4
-rw-r--r--app/views/home/initial_state.json.rabl2
-rwxr-xr-xapp/views/layouts/application.html.haml3
-rw-r--r--app/views/layouts/mailer.text.erb2
-rw-r--r--app/views/layouts/public.html.haml2
-rw-r--r--app/views/settings/imports/show.html.haml2
-rw-r--r--app/views/settings/preferences/show.html.haml2
-rw-r--r--app/views/shared/_landing_strip.html.haml2
-rw-r--r--app/views/stream_entries/show.html.haml2
-rw-r--r--app/views/user_mailer/confirmation_instructions.ja.html.erb8
-rw-r--r--app/views/user_mailer/confirmation_instructions.ja.text.erb8
-rw-r--r--app/views/user_mailer/reset_password_instructions.ja.html.erb2
-rw-r--r--app/views/user_mailer/reset_password_instructions.ja.text.erb2
57 files changed, 711 insertions, 207 deletions
diff --git a/app/assets/images/logo.png b/app/assets/images/logo.png
index 3ed93f120..f0c1c46c3 100644
--- a/app/assets/images/logo.png
+++ b/app/assets/images/logo.png
Binary files differdiff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg
index 52bf86b0e..c233db842 100644
--- a/app/assets/images/logo.svg
+++ b/app/assets/images/logo.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><g fill="#189efc"><path d="M500 0A500 500 0 0 0 0 500a500 500 0 0 0 500 500 500 500 0 0 0 500-500A500 500 0 0 0 500 0zm-2.5 271.1h107.24c-20.56 14.471-27.24 57.064-27.24 78.927v202.145c0 43.726-35.202 78.928-80 78.928s-80-35.202-80-78.928V350.027c0-43.725 35.202-78.927 80-78.927zm-276 48.9c44.798 0 80 35.202 80 78.928v202.144c0 21.863 6.68 64.456 27.24 78.928H221.5c-44.798 0-80-35.202-80-78.928V398.928c0-43.726 35.202-78.928 80-78.928zm550.24 0c44.799 0 80 35.202 80 78.928v202.144c0 43.726-35.201 78.928-80 78.928H664.5c20.56-14.472 27.24-57.065 27.24-78.928V398.928c0-43.726 35.202-78.928 80-78.928z"/><g transform="translate(-2)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g><g transform="matrix(1 0 0 -1 274 951)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g><g transform="matrix(-1 0 0 1 995 0)"><circle cx="223.5" cy="410.5" r="27.5"/><circle cx="223.5" cy="500.5" r="27.5"/><circle cx="223.5" cy="590.5" r="27.5"/></g></g></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><path d="M500 0a500 500 0 0 0-353.553 146.447 500 500 0 1 0 707.106 707.106A500 500 0 0 0 500 0zm-.059 280.05h107.12c-19.071 13.424-26.187 51.016-27.12 73.843V562.05c0 44.32-35.68 80-80 80s-80-35.68-80-80v-202c0-44.32 35.68-80 80-80zm-.441 52c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zm-279.059 7.9c44.32 0 80 35.68 80 80v206.157c.933 22.827 8.049 60.42 27.12 73.842H220.44c-44.32 0-80-35.68-80-80v-200c0-44.32 35.68-80 80-80zm559.12 0c44.32 0 80 35.68 80 80v200c0 44.32-35.68 80-80 80H672.44c19.071-13.424 26.187-51.016 27.12-73.843V419.95c0-44.32 35.68-80 80-80zM220 392c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm560 0c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm-280.5 40.05c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zM220 491.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zM499.5 532c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zM220 591.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28z" fill="#189efc"/></svg>
\ No newline at end of file
diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx
index 88e91c356..de75ddabe 100644
--- a/app/assets/javascripts/components/actions/compose.jsx
+++ b/app/assets/javascripts/components/actions/compose.jsx
@@ -73,9 +73,13 @@ export function mentionCompose(account, router) {
 
 export function submitCompose() {
   return function (dispatch, getState) {
+    const status = emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], ''));
+    if (!status || !status.length) {
+      return;
+    }
     dispatch(submitComposeRequest());
     api(getState).post('/api/v1/statuses', {
-      status: emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], '')),
+      status,
       in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
       media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
       sensitive: getState().getIn(['compose', 'sensitive']),
diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx
index 08576913e..185911861 100644
--- a/app/assets/javascripts/components/containers/mastodon.jsx
+++ b/app/assets/javascripts/components/containers/mastodon.jsx
@@ -57,6 +57,7 @@ import uk from 'react-intl/locale-data/uk';
 import zh from 'react-intl/locale-data/zh';
 import bg from 'react-intl/locale-data/bg';
 import { localeData as zh_hk } from '../locales/zh-hk';
+import pt_br from '../locales/pt-br';
 import getMessagesForLocale from '../locales';
 import { hydrateStore } from '../actions/store';
 import createStream from '../stream';
@@ -79,6 +80,7 @@ addLocaleData([
   ...hu,
   ...ja,
   ...pt,
+  ...pt_br,
   ...nl,
   ...no,
   ...ru,
diff --git a/app/assets/javascripts/components/features/compose/components/character_counter.jsx b/app/assets/javascripts/components/features/compose/components/character_counter.jsx
index e6b675354..fc64f94a5 100644
--- a/app/assets/javascripts/components/features/compose/components/character_counter.jsx
+++ b/app/assets/javascripts/components/features/compose/components/character_counter.jsx
@@ -9,14 +9,17 @@ const CharacterCounter = React.createClass({
 
   mixins: [PureRenderMixin],
 
+  checkRemainingText (diff) {
+    if (diff <= 0) {
+      return <span style={{ fontSize: '16px', cursor: 'default', color: '#ff5050' }}>{diff}</span>;
+    }
+    return <span style={{ fontSize: '16px', cursor: 'default' }}>{diff}</span>;
+  },
+
   render () {
     const diff = this.props.max - this.props.text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length;
 
-    return (
-      <span style={{ fontSize: '16px', cursor: 'default' }}>
-        {diff}
-      </span>
-    );
+    return this.checkRemainingText(diff);
   }
 
 });
diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx
index 0a1dd38ae..73a34ee4f 100644
--- a/app/assets/javascripts/components/locales/fr.jsx
+++ b/app/assets/javascripts/components/locales/fr.jsx
@@ -99,7 +99,7 @@ const fr = {
   "notifications.column_settings.mention": "Mentions :",
   "notifications.column_settings.reblog": "Partages :",
   "notifications.clear": "Nettoyer",
-  "notifications.clear_confirmation": "Voulez-vous vraiment nettoyer toutes vos notifications ?",
+  "notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?",
   "notifications.settings": "Paramètres de la colonne",
   "privacy.public.short": "Public",
   "privacy.public.long": "Afficher dans les fils publics",
@@ -123,7 +123,25 @@ const fr = {
   "report.heading": "Nouveau signalement",
   "report.placeholder": "Commentaires additionnels",
   "report.submit": "Envoyer",
-  "report.target": "Signalement"
+  "report.target": "Signalement",
+  "onboarding.next": "Suivant",
+  "onboarding.page_one.welcome": "Bienvenue sur Mastodon !",
+  "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⋅trice complet est {handle}",
+  "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.page_three.search": "Utilisez la barre de recherche pour trouver des utilisateurs⋅trices et regarder des hashtags tels que {illustration} et {introductions}. Pour trouver quelqu'un qui n'est pas sur cette instance, utilisez son nom d'utilisateur⋅trice complet.",
+  "onboarding.page_three.profile": "Modifiez votre profil pour changer votre avatar, votre description ainsi que votre nom. Vous y trouverez également d'autres préférences.",
+  "onboarding.page_four.home": "L'Accueil affiche les posts de tou⋅te⋅s les utilisateurs⋅trices que vous suivez",
+  "onboarding.page_four.notifications": "Les Notifications vous informent lorsque quelqu'un interagit avec vous",
+  "onboarding.page_five.public_timelines": "Le fil public global affiche les posts de tou⋅te⋅s les utilisateurs⋅trices suivi⋅es par les membres de {domain}. Le fil public local est identique mais se limite aux utilisateurs⋅trices de {domain}.",
+  "onboarding.page_six.almost_done": "Nous y sommes presque...",
+  "onboarding.page_six.admin": "L'administrateur⋅trice de votre instance est {admin}",
+  "onboarding.page_six.read_guidelines": "S'il vous plaît, n'oubliez pas de lire les {guidelines} !",
+  "onboarding.page_six.guidelines": "règles de la communauté",
+  "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.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant... Bon Appetoot!",
+  "onboarding.page_six.various_app": "applications mobiles",
+  "onboarding.skip": "Passer",
 };
 
 export default fr;
diff --git a/app/assets/javascripts/components/locales/hr.jsx b/app/assets/javascripts/components/locales/hr.jsx
new file mode 100644
index 000000000..c26e2cc29
--- /dev/null
+++ b/app/assets/javascripts/components/locales/hr.jsx
@@ -0,0 +1,124 @@
+/**
+hrvatski jezik
+ */
+const hr = {
+  "account.block": "Blokiraj @{name}",
+  "account.disclaimer": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.",
+  "account.edit_profile": "Uredi profil",
+  "account.follow": "Slijedi",
+  "account.followers": "Sljedbenici",
+  "account.follows_you": "te slijedi",
+  "account.follows": "Slijedi",
+  "account.mention": "Spomeni @{name}",
+  "account.mute": "Utišaj @{name}",
+  "account.posts": "Postovi",
+  "account.report": "Prijavi @{name}",
+  "account.requested": "Čeka pristanak",
+  "account.unblock": "Deblokiraj @{name}",
+  "account.unfollow": "Prestani slijediti",
+  "account.unmute": "Poništi utišavanje @{name}",
+  "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put",
+  "column_back_button.label": "Natrag",
+  "column.blocks": "Blokirani korisnici",
+  "column.community": "Lokalni timeline",
+  "column.favourites": "Favoriti",
+  "column.follow_requests": "Zahtjevi za slijeđenje",
+  "column.home": "Dom",
+  "column.notifications": "Notifikacije",
+  "column.public": "Federalni timeline",
+  "compose_form.placeholder": "Što ti je na umu?",
+  "compose_form.privacy_disclaimer": "Tvoj privatni status će biti dostavljen spomenutim korisnicima na {domains}. Vjeruješ li {domainsCount, plural, one {that server} drugim {those servers}}? Privatnost postova radi samo na Mastodon instancama. Ako {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, neće biti indikacije da je tvoj post privatan, i mogao bit biti boosted ili biti učinjen vidljivim na drugi način neželjenim primateljima.",
+  "compose_form.publish": "Toot",
+  "compose_form.sensitive": "Označi media sadržaj kao osjetljiv",
+  "compose_form.spoiler_placeholder": "Upozorenje o sadržaju",
+  "compose_form.spoiler": "Sakrij text iza upozorenja",
+  "emoji_button.label": "Umetni smajlije",
+  "empty_column.community": "Lokalni timeline je prazan. Napiši nešto javno kako bi pokrenuo stvari!",
+  "empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.",
+  "empty_column.home.public_timeline": "javni timeline",
+  "empty_column.home": "Još ne slijediš nikoga. Posjeti {public} ili koristi tražilicu kako bi počeo i upoznao druge korisnike.",
+  "empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.",
+  "empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio",
+  "follow_request.authorize": "Authoriziraj",
+  "follow_request.reject": "Odbij",
+  "getting_started.apps": "Dostupne su razne aplikacije",
+  "getting_started.heading": "Počnimo",
+  "getting_started.open_source_notice": "Mastodon je softver otvorenog koda. Možeš pridonijeti ili prijaviti probleme na GitHubu  {github}. {apps}.",
+  "home.column_settings.advanced": "Napredno",
+  "home.column_settings.basic": "Osnovno",
+  "home.column_settings.filter_regex": "Filtriraj s regularnim izrazima",
+  "home.column_settings.show_reblogs": "Pokaži boosts",
+  "home.column_settings.show_replies": "Pokaži odgovore",
+  "home.settings": "Postavke Stupca",
+  "lightbox.close": "Zatvori",
+  "loading_indicator.label": "Učitavam...",
+  "media_gallery.toggle_visible": "Preklopi vidljivost",
+  "missing_indicator.label": "Nije nađen",
+  "navigation_bar.blocks": "Blokirani korisnici",
+  "navigation_bar.community_timeline": "Lokalni timeline",
+  "navigation_bar.edit_profile": "Uredi profil",
+  "navigation_bar.favourites": "Favoriti",
+  "navigation_bar.follow_requests": "Zahtjevi za sljeđenje",
+  "navigation_bar.info": "Proširena informacija",
+  "navigation_bar.logout": "Odjavi se",
+  "navigation_bar.preferences": "Postavke",
+  "navigation_bar.public_timeline": "Federalni timeline",
+  "notification.favourite": "{name} je lajkao tvoj status",
+  "notification.follow": "{name} te sada slijedi",
+  "notification.reblog": "{name} je boosted tvoj status",
+  "notifications.clear_confirmation": "Želiš li zaista obrisati sve svoje notifikacije?",
+  "notifications.clear": "Očisti notifikacije",
+  "notifications.column_settings.alert": "Desktop notifikacije",
+  "notifications.column_settings.favourite": "Favoriti:",
+  "notifications.column_settings.follow": "Novi sljedbenici:",
+  "notifications.column_settings.mention": "Spominjanja:",
+  "notifications.column_settings.reblog": "Boosts:",
+  "notifications.column_settings.show": "Prikaži u stupcu",
+  "notifications.column_settings.sound": "Sviraj zvuk",
+  "notifications.settings": "Postavke rubrike",
+  "privacy.change": "Podesi status privatnosti",
+  "privacy.direct.long": "Prikaži samo spomenutim korisnicima",
+  "privacy.direct.short": "Direktno",
+  "privacy.private.long": "Prikaži samo sljedbenicima",
+  "privacy.private.short": "Privatno",
+  "privacy.public.long": "Postaj na javne timeline",
+  "privacy.public.short": "Javno",
+  "privacy.unlisted.long": "Ne prikazuj u javnim timelines",
+  "privacy.unlisted.short": "Unlisted",
+  "reply_indicator.cancel": "Otkaži",
+  "report.heading": "Nova prijava",
+  "report.placeholder": "Dodatni komentari",
+  "report.submit": "Pošalji",
+  "report.target": "Prijavljivanje",
+  "search_results.total": "{count} {count, plural, one {result} other {results}}",
+  "search.placeholder": "Traži",
+  "search.status_by": "Status od {name}",
+  "status.delete": "Obriši",
+  "status.favourite": "Označi omiljenim",
+  "status.load_more": "Učitaj više",
+  "status.media_hidden": "Sakriven media sadržaj",
+  "status.mention": "Spomeni @{name}",
+  "status.open": "Proširi ovaj status",
+  "status.reblog": "Boost",
+  "status.reblogged_by": "{name} boosted",
+  "status.reply": "Odgovori",
+  "status.report": "Prijavi @{name}",
+  "status.sensitive_toggle": "Klikni da bi vidio",
+  "status.sensitive_warning": "Osjetljiv sadržaj",
+  "status.show_less": "Pokaži manje",
+  "status.show_more": "Pokaži više",
+  "tabs_bar.compose": "Sastavi",
+  "tabs_bar.federated_timeline": "Federalni",
+  "tabs_bar.home": "Dom",
+  "tabs_bar.local_timeline": "Lokalno",
+  "tabs_bar.notifications": "Notifikacije",
+  "upload_area.title": "Povuci & spusti kako bi uploadao",
+  "upload_button.label": "Dodaj media",
+  "upload_form.undo": "Poništi",
+  "upload_progress.label": "Uploadam...",
+  "video_player.toggle_sound": "Toggle zvuk",
+  "video_player.toggle_visible": "Preklopi vidljivost",
+  "video_player.expand": "Proširi video",
+};
+
+export default hr;
diff --git a/app/assets/javascripts/components/locales/index.jsx b/app/assets/javascripts/components/locales/index.jsx
index 7525022b1..7abb315da 100644
--- a/app/assets/javascripts/components/locales/index.jsx
+++ b/app/assets/javascripts/components/locales/index.jsx
@@ -1,11 +1,13 @@
 import en from './en';
 import de from './de';
 import es from './es';
+import hr from './hr';
 import hu from './hu';
 import fr from './fr';
 import nl from './nl';
 import no from './no';
 import pt from './pt';
+import pt_br from './pt-br';
 import uk from './uk';
 import fi from './fi';
 import eo from './eo';
@@ -18,11 +20,13 @@ const locales = {
   en,
   de,
   es,
+  hr,
   hu,
   fr,
   nl,
   no,
   pt,
+  'pt-BR': pt_br,
   uk,
   fi,
   eo,
diff --git a/app/assets/javascripts/components/locales/nl.jsx b/app/assets/javascripts/components/locales/nl.jsx
index 8fc3a422f..533bc2aa5 100644
--- a/app/assets/javascripts/components/locales/nl.jsx
+++ b/app/assets/javascripts/components/locales/nl.jsx
@@ -22,47 +22,69 @@ const nl = {
   "account.followers": "Volgers",
   "account.follows_you": "Volgt jou",
   "account.requested": "Wacht op goedkeuring",
+  "account.mute": "@{name} negeren",
+  "account.unmute": "@{name} niet meer negeren",
+  "account.report": "Report @{name}",
   "getting_started.heading": "Beginnen",
-  "getting_started.about_addressing": "Je kunt mensen volgen als je hun gebruikersnaam en het domein van hun server kent, door het e-mailachtige adres in het zoekscherm in te voeren.",
-  "getting_started.about_shortcuts": "Als de gezochte gebruiker op hetzelfde domein zit als jijzelf, is invoeren van de gebruikersnaam genoeg. Dat geldt ook als je mensen in de statussen wilt vermelden.",
+  "getting_started.about_addressing": "Je kunt mensen volgen als je hun gebruikersnaam en het domein van hun server kent. Voer hiervoor het e-mailachtige adres in het zoekveld in.",
+  "getting_started.about_shortcuts": "Als de gezochte gebruiker op hetzelfde domein zit als jijzelf, is invoeren van de gebruikersnaam genoeg. Dat geldt ook als je mensen in toots wilt vermelden.",
   "getting_started.open_source_notice": "Mastodon is open-sourcesoftware. Je kunt bijdragen of problemen melden op GitHub via {github}. {apps}.",
-  "column.home": "Thuis",
+  "column.home": "Jouw tijdlijn",
   "column.community": "Lokale tijdlijn",
-  "column.public": "Federatietijdlijn",
+  "column.public": "Globale tijdlijn",
   "column.notifications": "Meldingen",
   "tabs_bar.compose": "Schrijven",
-  "tabs_bar.home": "Thuis",
+  "tabs_bar.home": "Jouw tijdlijn",
   "tabs_bar.mentions": "Vermeldingen",
-  "tabs_bar.public": "Federatietijdlijn",
+  "tabs_bar.public": "Globale tijdlijn",
   "tabs_bar.notifications": "Meldingen",
   "compose_form.placeholder": "Waar ben je mee bezig?",
   "compose_form.publish": "Toot",
   "compose_form.sensitive": "Media als gevoelig markeren",
   "compose_form.spoiler": "Tekst achter waarschuwing verbergen",
+  "compose_form.spoiler_placeholder": "Waarschuwingstekst",
   "compose_form.private": "Als privé markeren",
-  "compose_form.privacy_disclaimer": "Je besloten status wordt afgeleverd aan vermelde gebruikers op {domains}. Vertrouw je {domainsCount, plural, one {that server} andere {those servers}}? Privé plaatsen werkt alleen op Mastodon servers. Als {domains} {domainsCount, plural, een {is not a Mastodon instance} andere {are not Mastodon instances}}, dan wordt er geen indicatie gegeven dat he bericht besloten is, waardoor het kan worden geboost of op andere manier zichtbaar worden voor niet bedoelde lezers.",
+  "compose_form.privacy_disclaimer": "Jouw privétoot wordt afgeleverd aan de vermelde gebruikers op {domains}. Vertrouw jij {domainsCount, plural, one {that server} andere {those servers}}? Het privé plaatsen van toots werkt alleen op Mastodon-servers. Als {domains} {domainsCount, plural, een {is not a Mastodon instance} andere {are not Mastodon instances}}, dan wordt er niet aangegeven dat de toot besloten is, waardoor het kan worden geboost of op een andere manier zichtbaar wordt gemaakt voor mensen waarvoor het niet was bedoeld.",
   "compose_form.unlisted": "Niet op openbare tijdlijnen tonen",
   "navigation_bar.edit_profile": "Profiel bewerken",
   "navigation_bar.preferences": "Voorkeuren",
   "navigation_bar.community_timeline": "Lokale tijdlijn",
-  "navigation_bar.public_timeline": "Federatietijdlijn",
+  "navigation_bar.public_timeline": "Globale tijdlijn",
+  "navigation_bar.follow_requests": "Volgverzoeken",
+  "navigation_bar.info": "Uitgebreide informatie",
+  "navigation_bar.blocks": "Geblokkeerde gebruikers",
+  "navigation_bar.mutes": "Genegeerde gebruikers",
   "navigation_bar.logout": "Afmelden",
   "reply_indicator.cancel": "Annuleren",
   "search.placeholder": "Zoeken",
   "search.account": "Account",
   "search.hashtag": "Hashtag",
+  "search_results.total": "{count} {count, plural, one {resultaat} other {resultaten}}",
   "upload_button.label": "Media toevoegen",
   "upload_form.undo": "Ongedaan maken",
-  "notification.follow": "{name} volgde jou",
-  "notification.favourite": "{name} markeerde je status als favoriet",
-  "notification.reblog": "{name} boostte je status",
+  "notification.follow": "{name} volgt jou nu",
+  "notification.favourite": "{name} markeerde jouw toot als favoriet",
+  "notification.reblog": "{name} boostte jouw toot",
   "notification.mention": "{name} vermeldde jou",
+  "notifications.clear_confirmation": "Weet je zeker dat je al jouw meldingen wilt verwijderen?",
+  "notifications.clear": "Meldingen verwijderen",
   "notifications.column_settings.alert": "Desktopmeldingen",
   "notifications.column_settings.show": "In kolom tonen",
   "notifications.column_settings.follow": "Nieuwe volgers:",
   "notifications.column_settings.favourite": "Favorieten:",
   "notifications.column_settings.mention": "Vermeldingen:",
   "notifications.column_settings.reblog": "Boosts:",
+  "notifications.column_settings.sound": "Geluid afspelen",
+  "notifications.settings": "Kolom-instellingen",
+  "privacy.change": "Privacy toot aanpassen",
+  "privacy.direct.long": "Toot alleen naar vermelde gebruikers",
+  "privacy.direct.short": "Direct",
+  "privacy.private.long": "Toot alleen naar jouw volgers",
+  "privacy.private.short": "Privé",
+  "privacy.public.long": "Toot naar openbare tijdlijnen",
+  "privacy.public.short": "Openbaar",
+  "privacy.unlisted.long": "Niet op openbare tijdlijnen weergeven",
+  "privacy.unlisted.short": "Minder openbaar",
 };
 
 export default nl;
diff --git a/app/assets/javascripts/components/locales/no.jsx b/app/assets/javascripts/components/locales/no.jsx
index 43715fb5c..c89c5ede6 100644
--- a/app/assets/javascripts/components/locales/no.jsx
+++ b/app/assets/javascripts/components/locales/no.jsx
@@ -33,7 +33,7 @@ const no = {
   "empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!",
   "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.",
   "empty_column.home.public_timeline": "en offentlig tidslinje",
-  "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": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.",
   "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",
diff --git a/app/assets/javascripts/components/locales/pt-br.jsx b/app/assets/javascripts/components/locales/pt-br.jsx
new file mode 100644
index 000000000..724c5f1ce
--- /dev/null
+++ b/app/assets/javascripts/components/locales/pt-br.jsx
@@ -0,0 +1,125 @@
+const pt_br = {
+  "account.block": "Bloquear @{name}",
+  "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
+  "account.edit_profile": "Editar perfil",
+  "account.follow": "Seguir",
+  "account.followers": "Seguidores",
+  "account.follows_you": "É teu seguidor",
+  "account.follows": "Segue",
+  "account.mention": "Mencionar @{name}",
+  "account.mute": "Silenciar @{name}",
+  "account.posts": "Posts",
+  "account.report": "Denunciar @{name}",
+  "account.requested": "A aguardar aprovação",
+  "account.unblock": "Não bloquear @{name}",
+  "account.unfollow": "Deixar de seguir",
+  "account.unmute": "Não silenciar @{name}",
+  "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
+  "column_back_button.label": "Voltar",
+  "column.blocks": "Utilizadores Bloqueados",
+  "column.community": "Local",
+  "column.favourites": "Favoritos",
+  "column.follow_requests": "Seguidores Pendentes",
+  "column.home": "Home",
+  "column.mutes": "Utilizadores silenciados",
+  "column.notifications": "Notificações",
+  "column.public": "Global",
+  "compose_form.placeholder": "Em que estás a pensar?",
+  "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
+  "compose_form.publish": "Publicar",
+  "compose_form.sensitive": "Marcar media como conteúdo sensível",
+  "compose_form.spoiler_placeholder": "Aviso de conteúdo",
+  "compose_form.spoiler": "Esconder texto com aviso",
+  "emoji_button.label": "Inserir Emoji",
+  "empty_column.community": "Ainda não existem conteúdo local para mostrar!",
+  "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag",
+  "empty_column.home.public_timeline": "global",
+  "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
+  "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
+  "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
+  "follow_request.authorize": "Autorizar",
+  "follow_request.reject": "Rejeitar",
+  "getting_started.apps": "Existem várias aplicações disponíveis",
+  "getting_started.heading": "Primeiros passos",
+  "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
+  "home.column_settings.advanced": "Avançado",
+  "home.column_settings.basic": "Básico",
+  "home.column_settings.filter_regex": "Filtrar com uma expressão regular",
+  "home.column_settings.show_reblogs": "Mostrar as partilhas",
+  "home.column_settings.show_replies": "Mostrar as respostas",
+  "home.settings": "Parâmetros da listagem Home",
+  "lightbox.close": "Fechar",
+  "loading_indicator.label": "Carregando...",
+  "media_gallery.toggle_visible": "Esconder/Mostrar",
+  "missing_indicator.label": "Não encontrado",
+  "navigation_bar.blocks": "Utilizadores bloqueados",
+  "navigation_bar.community_timeline": "Local",
+  "navigation_bar.edit_profile": "Editar perfil",
+  "navigation_bar.favourites": "Favoritos",
+  "navigation_bar.follow_requests": "Seguidores pendentes",
+  "navigation_bar.info": "Mais informações",
+  "navigation_bar.logout": "Sair",
+  "navigation_bar.mutes": "Utilizadores silenciados",
+  "navigation_bar.preferences": "Preferências",
+  "navigation_bar.public_timeline": "Global",
+  "notification.favourite": "{name} adicionou o teu post aos favoritos",
+  "notification.follow": "{name} seguiu-te",
+  "notification.mention": "{name} mencionou-te",
+  "notification.reblog": "{name} partilhou o teu post",
+  "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
+  "notifications.clear": "Limpar notificações",
+  "notifications.column_settings.alert": "Notificações no computador",
+  "notifications.column_settings.favourite": "Favoritos:",
+  "notifications.column_settings.follow": "Novos seguidores:",
+  "notifications.column_settings.mention": "Menções:",
+  "notifications.column_settings.reblog": "Partilhas:",
+  "notifications.column_settings.show": "Mostrar nas colunas",
+  "notifications.column_settings.sound": "Reproduzir som",
+  "notifications.settings": "Parâmetros da listagem de Notificações",
+  "privacy.change": "Ajustar a privacidade da mensagem",
+  "privacy.direct.long": "Apenas para utilizadores mencionados",
+  "privacy.direct.short": "Directo",
+  "privacy.private.long": "Apenas para os seguidores",
+  "privacy.private.short": "Privado",
+  "privacy.public.long": "Publicar em todos os feeds",
+  "privacy.public.short": "Público",
+  "privacy.unlisted.long": "Não publicar nos feeds públicos",
+  "privacy.unlisted.short": "Não listar",
+  "reply_indicator.cancel": "Cancelar",
+  "report.heading": "Nova denúncia",
+  "report.placeholder": "Comentários adicionais",
+  "report.submit": "Enviar",
+  "report.target": "Denunciar",
+  "search_results.total": "{count} {count, plural, one {resultado} other {resultados}}",
+  "search.placeholder": "Pesquisar",
+  "search.status_by": "Post de {name}",
+  "status.delete": "Eliminar",
+  "status.favourite": "Adicionar aos favoritos",
+  "status.load_more": "Carregar mais",
+  "status.media_hidden": "Media escondida",
+  "status.mention": "Mencionar @{name}",
+  "status.open": "Expandir",
+  "status.reblog": "Partilhar",
+  "status.reblogged_by": "{name} partilhou",
+  "status.reply": "Responder",
+  "status.report": "Denúnciar @{name}",
+  "status.sensitive_toggle": "Clique para ver",
+  "status.sensitive_warning": "Conteúdo sensível",
+  "status.show_less": "Mostrar menos",
+  "status.show_more": "Mostrar mais",
+  "tabs_bar.compose": "Criar",
+  "tabs_bar.federated_timeline": "Global",
+  "tabs_bar.home": "Home",
+  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.notifications": "Notificações",
+  "upload_area.title": "Arraste e solte para enviar",
+  "upload_button.label": "Adicionar media",
+  "upload_form.undo": "Anular",
+  "upload_progress.label": "A gravar...",
+  "video_player.toggle_sound": "Ligar/Desligar som",
+  "video_player.toggle_visible": "Ligar/Desligar vídeo",
+  "video_player.expand": "Expandir vídeo",
+  "video_player.video_error": "Não é possível ver o vídeo",
+};
+
+export default pt_br;
diff --git a/app/assets/javascripts/components/locales/pt.jsx b/app/assets/javascripts/components/locales/pt.jsx
index cd345a585..88729c94c 100644
--- a/app/assets/javascripts/components/locales/pt.jsx
+++ b/app/assets/javascripts/components/locales/pt.jsx
@@ -1,128 +1,125 @@
 const pt = {
-  "column_back_button.label": "Voltar",
-  "lightbox.close": "Fechar",
-  "loading_indicator.label": "Carregando...",
-  "status.mention": "Mencionar @{name}",
-  "status.delete": "Eliminar",
-  "status.reply": "Responder",
-  "status.reblog": "Partilhar",
-  "status.favourite": "Adicionar aos favoritos",
-  "status.reblogged_by": "{name} partilhou",
-  "status.sensitive_warning": "Conteúdo sensível",
-  "status.sensitive_toggle": "Clique para ver",
-  "status.show_more": "Mostrar mais",
-  "status.show_less": "Mostrar menos",
-  "status.open": "Expandir",
-  "status.report": "Reportar @{name}",
-  "status.load_more": "Carregar mais",
-  "status.media_hidden": "Media escondida",
-  "video_player.toggle_sound": "Ligar/Desligar som",
-  "video_player.toggle_visible": "Ligar/Desligar vídeo",
-  "account.mention": "Mencionar @{name}",
-  "account.edit_profile": "Editar perfil",
-  "account.unblock": "Não bloquear @{name}",
-  "account.unfollow": "Não seguir",
   "account.block": "Bloquear @{name}",
-  "account.mute": "Mute",
-  "account.unmute": "Remover Mute",
+  "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
+  "account.edit_profile": "Editar perfil",
   "account.follow": "Seguir",
-  "account.posts": "Posts",
-  "account.follows": "Segue",
   "account.followers": "Seguidores",
   "account.follows_you": "É teu seguidor",
+  "account.follows": "Segue",
+  "account.mention": "Mencionar @{name}",
+  "account.mute": "Silenciar @{name}",
+  "account.posts": "Posts",
+  "account.report": "Denunciar @{name}",
   "account.requested": "A aguardar aprovação",
-  "account.report": "Denunciar",
-  "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
-  "getting_started.heading": "Primeiros passos",
-  "getting_started.about_addressing": "Podes seguir pessoas se sabes o nome de usuário deles e o domínio em que estão colocando um endereço similar a e-mail no campo no topo da barra lateral.",
-  "getting_started.about_shortcuts": "Se o usuário alvo está no mesmo domínio, só o nome funcionará. A mesma regra se aplica a mencionar pessoas nas postagens.",
-  "getting_started.about_developer": "Pode seguir o developer deste projecto em Gargron@mastodon.social",
-  "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
-  "column.home": "Home",
-  "column.community": "Local",
-  "column.public": "Global",
-  "column.notifications": "Notificações",
+  "account.unblock": "Não bloquear @{name}",
+  "account.unfollow": "Deixar de seguir",
+  "account.unmute": "Não silenciar @{name}",
+  "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
+  "column_back_button.label": "Voltar",
   "column.blocks": "Utilizadores Bloqueados",
+  "column.community": "Local",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Seguidores Pendentes",
-  "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
-  "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
-  "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
-  "empty_column.home.public_timeline": "global",
-  "empty_column.community": "Ainda não existem conteúdo local para mostrar!",
-  "empty_column.hashtag": "Não existe qualquer conteúdo com essa hashtag",
-  "tabs_bar.compose": "Criar",
-  "tabs_bar.home": "Home",
-  "tabs_bar.mentions": "Menções",
-  "tabs_bar.public": "Público",
-  "tabs_bar.notifications": "Notificações",
-  "tabs_bar.local_timeline": "Local",
-  "tabs_bar.federated_timeline": "Global",
+  "column.home": "Home",
+  "column.mutes": "Utilizadores silenciados",
+  "column.notifications": "Notificações",
+  "column.public": "Global",
   "compose_form.placeholder": "Em que estás a pensar?",
+  "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
   "compose_form.publish": "Publicar",
   "compose_form.sensitive": "Marcar media como conteúdo sensível",
+  "compose_form.spoiler_placeholder": "Aviso de conteúdo",
   "compose_form.spoiler": "Esconder texto com aviso",
-  "compose_form.spoiler_placeholder": "Aviso",
-  "compose_form.private": "Tornar privado",
-  "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
-  "compose_form.unlisted": "Não mostrar na listagem pública",
   "emoji_button.label": "Inserir Emoji",
-  "navigation_bar.edit_profile": "Editar perfil",
-  "navigation_bar.preferences": "Preferências",
-  "navigation_bar.community_timeline": "Local",
-  "navigation_bar.public_timeline": "Global",
+  "empty_column.community": "Ainda não existem conteúdo local para mostrar!",
+  "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag",
+  "empty_column.home.public_timeline": "global",
+  "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
+  "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
+  "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
+  "follow_request.authorize": "Autorizar",
+  "follow_request.reject": "Rejeitar",
+  "getting_started.apps": "Existem várias aplicações disponíveis",
+  "getting_started.heading": "Primeiros passos",
+  "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
+  "home.column_settings.advanced": "Avançado",
+  "home.column_settings.basic": "Básico",
+  "home.column_settings.filter_regex": "Filtrar com uma expressão regular",
+  "home.column_settings.show_reblogs": "Mostrar as partilhas",
+  "home.column_settings.show_replies": "Mostrar as respostas",
+  "home.settings": "Parâmetros da listagem Home",
+  "lightbox.close": "Fechar",
+  "loading_indicator.label": "Carregando...",
+  "media_gallery.toggle_visible": "Esconder/Mostrar",
+  "missing_indicator.label": "Não encontrado",
   "navigation_bar.blocks": "Utilizadores bloqueados",
+  "navigation_bar.community_timeline": "Local",
+  "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritos",
+  "navigation_bar.follow_requests": "Seguidores pendentes",
   "navigation_bar.info": "Mais informações",
   "navigation_bar.logout": "Sair",
-  "navigation_bar.follow_requests": "Seguidores pendentes",
-  "reply_indicator.cancel": "Cancelar",
-  "search.placeholder": "Pesquisar",
-  "search.account": "Conta",
-  "search.hashtag": "Hashtag",
-  "search_results.total": "{count} {count, plural, one {resultado} other {resultados}}",
-  "search.status_by": "Post de {name}",
-  "upload_button.label": "Adicionar media",
-  "upload_form.undo": "Anular",
-  "upload_progress.label": "A gravar…",
-  "upload_area.title": "Arraste e solte para enviar",
-  "notification.follow": "{name} seguiu-te",
+  "navigation_bar.mutes": "Utilizadores silenciados",
+  "navigation_bar.preferences": "Preferências",
+  "navigation_bar.public_timeline": "Global",
   "notification.favourite": "{name} adicionou o teu post aos favoritos",
-  "notification.reblog": "{name} partilhou o teu post",
+  "notification.follow": "{name} seguiu-te",
   "notification.mention": "{name} mencionou-te",
+  "notification.reblog": "{name} partilhou o teu post",
+  "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
+  "notifications.clear": "Limpar notificações",
   "notifications.column_settings.alert": "Notificações no computador",
-  "notifications.column_settings.show": "Mostrar nas colunas",
-  "notifications.column_settings.sound": "Reproduzir som",
-  "notifications.column_settings.follow": "Novos seguidores:",
   "notifications.column_settings.favourite": "Favoritos:",
+  "notifications.column_settings.follow": "Novos seguidores:",
   "notifications.column_settings.mention": "Menções:",
   "notifications.column_settings.reblog": "Partilhas:",
-  "notifications.clear": "Limpar notificações",
-  "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
-  "notifications.settings": "Parâmetros da lista de Notificações",
-  "privacy.public.short": "Público",
+  "notifications.column_settings.show": "Mostrar nas colunas",
+  "notifications.column_settings.sound": "Reproduzir som",
+  "notifications.settings": "Parâmetros da listagem de Notificações",
+  "privacy.change": "Ajustar a privacidade da mensagem",
+  "privacy.direct.long": "Apenas para utilizadores mencionados",
+  "privacy.direct.short": "Directo",
+  "privacy.private.long": "Apenas para os seguidores",
+  "privacy.private.short": "Privado",
   "privacy.public.long": "Publicar em todos os feeds",
-  "privacy.unlisted.short": "Não listar",
+  "privacy.public.short": "Público",
   "privacy.unlisted.long": "Não publicar nos feeds públicos",
-  "privacy.private.short": "Privado",
-  "privacy.private.long": "Apenas para os seguidores",
-  "privacy.direct.short": "Directo",
-  "privacy.direct.long": "Apenas para utilizadores mencionados",
-  "privacy.change": "Ajustar a privacidade da mensagem",
-  "media_gallery.toggle_visible": "Modificar a visibilidade",
-  "missing_indicator.label": "Não encontrado",
-  "follow_request.authorize": "Autorizar",
-  "follow_request.reject": "Rejeitar",
-  "home.settings": "Parâmetros da coluna Home",
-  "home.column_settings.basic": "Básico",
-  "home.column_settings.show_reblogs": "Mostrar as partilhas",
-  "home.column_settings.show_replies": "Mostrar as respostas",
-  "home.column_settings.advanced": "Avançadas",
-  "home.column_settings.filter_regex": "Filtrar com uma expressão regular",
-  "report.heading": "Nova denuncia",
+  "privacy.unlisted.short": "Não listar",
+  "reply_indicator.cancel": "Cancelar",
+  "report.heading": "Nova denúncia",
   "report.placeholder": "Comentários adicionais",
   "report.submit": "Enviar",
-  "report.target": "Denunciar"
+  "report.target": "Denunciar",
+  "search_results.total": "{count} {count, plural, one {resultado} other {resultados}}",
+  "search.placeholder": "Pesquisar",
+  "search.status_by": "Post de {name}",
+  "status.delete": "Eliminar",
+  "status.favourite": "Adicionar aos favoritos",
+  "status.load_more": "Carregar mais",
+  "status.media_hidden": "Media escondida",
+  "status.mention": "Mencionar @{name}",
+  "status.open": "Expandir",
+  "status.reblog": "Partilhar",
+  "status.reblogged_by": "{name} partilhou",
+  "status.reply": "Responder",
+  "status.report": "Denúnciar @{name}",
+  "status.sensitive_toggle": "Clique para ver",
+  "status.sensitive_warning": "Conteúdo sensível",
+  "status.show_less": "Mostrar menos",
+  "status.show_more": "Mostrar mais",
+  "tabs_bar.compose": "Criar",
+  "tabs_bar.federated_timeline": "Global",
+  "tabs_bar.home": "Home",
+  "tabs_bar.local_timeline": "Local",
+  "tabs_bar.notifications": "Notificações",
+  "upload_area.title": "Arraste e solte para enviar",
+  "upload_button.label": "Adicionar media",
+  "upload_form.undo": "Anular",
+  "upload_progress.label": "A gravar...",
+  "video_player.toggle_sound": "Ligar/Desligar som",
+  "video_player.toggle_visible": "Ligar/Desligar vídeo",
+  "video_player.expand": "Expandir vídeo",
+  "video_player.video_error": "Não é possível ver o vídeo",
 };
 
 export default pt;
diff --git a/app/assets/javascripts/components/locales/zh-cn.jsx b/app/assets/javascripts/components/locales/zh-cn.jsx
new file mode 100644
index 000000000..67baa02a2
--- /dev/null
+++ b/app/assets/javascripts/components/locales/zh-cn.jsx
@@ -0,0 +1,157 @@
+import zh from 'react-intl/locale-data/zh';
+
+const localeData = zh.reduce(function (acc, localeData) {
+  if (localeData.locale === "zh-Hans-CN") {
+    // rename the locale "zh-Hans-CN" as "zh-CN"
+    // (match the code usually used in Accepted-Language header)
+    acc.push(Object.assign({},
+      localeData,
+      {
+        "locale": "zh-CN",
+        "parentLocale": "zh-Hans-CN",
+      }
+    ));
+  }
+  return acc;
+}, []);
+
+export { localeData as localeData };
+
+const zh_cn = {
+  "account.block": "屏蔽 @{name}",
+  "account.disclaimer": "由于这个账户处于另一个服务站,实际数字会比这个更多。",
+  "account.edit_profile": "修改个人资料",
+  "account.follow": "关注",
+  "account.followers": "关注的人",
+  "account.follows_you": "关注你",
+  "account.follows": "正在关注",
+  "account.mention": "提及 @{name}",
+  "account.mute": "将 @{name} 静音",
+  "account.posts": "嘟文",
+  "account.report": "举报 @{name}",
+  "account.requested": "等候审批",
+  "account.unblock": "解除对 @{name} 的屏蔽",
+  "account.unfollow": "取消关注",
+  "account.unmute": "取消 @{name} 的静音",
+  "boost_modal.combo": "如你想在下次路过时显示,请按{combo},",
+  "column_back_button.label": "返回",
+  "column.blocks": "屏蔽用户",
+  "column.community": "本站时间轴",
+  // intentional departure from existing "推文" translation for posts:
+  // "推文" refers to "推特", the official translation for Twitter.
+  // Currently using a semi-phonetic translation "嘟", which refers
+  // to train horn sounds, for "toot".
+  "column.favourites": "赞过的嘟文",
+  "column.follow_requests": "关注请求",
+  "column.home": "主页",
+  "column.notifications": "通知",
+  "column.public": "跨站公共时间轴",
+  "compose_form.placeholder": "在想啥?",
+  "compose_form.privacy_disclaimer": "你的私人嘟文,将被发送至你所提及的 {domains} 用户。你是否信任 {domainsCount, plural, one {这个网站} other {这些网站}}?请留意,嘟文隐私设置只适用于各 Mastodon 服务站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服务站} other {之中有些不是 Mastodon 服务站}},对方将无法收到这篇嘟文的隐私设置,然后可能被转嘟给不能预知的用户阅读。",
+  "compose_form.private": "标示为“只有关注你的人能看”",
+  // Going "toot-toot!" here below.
+  "compose_form.publish": "嘟嘟!",
+  "compose_form.sensitive": "将媒体文件标示为“敏感内容”",
+  "compose_form.spoiler_placeholder": "敏感内容",
+  "compose_form.spoiler": "将部份文本藏于警告消息之后",
+  "compose_form.unlisted": "请勿在公共时间轴显示",
+  "emoji_button.label": "加入表情符号",
+  "empty_column.community": "本站时间轴暂时未有内容,快贴文来抢头香啊!",
+  "empty_column.hashtag": "这个标签暂时未有内容。",
+  "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
+  "empty_column.home.public_timeline": "公共时间轴",
+  "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
+  "empty_column.notifications": "你没有任何通知纪录,快向其他用户搭讪吧。",
+  "empty_column.public": "跨站公共时间轴暂时没有内容!快写一些公共的嘟文,或者关注另一些服务站的用户吧!你和本站、友站的交流,将决定这里出现的内容。",
+  "follow_request.authorize": "批准",
+  "follow_request.reject": "拒绝",
+  "getting_started.about_addressing": "只要你知道一位用户的用户名称和域名,你可以用“@用户名称@域名”的格式在搜索栏寻找该用户。",
+  "getting_started.about_shortcuts": "只要该用户是在你现在的服务站开立,你可以直接输入用户𠱷搜索。同样的规则适用于在嘟文提及别的用户。",
+  "getting_started.apps": "手机或桌面应用程序",
+  "getting_started.heading": "开始使用",
+  "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。你亦可透过{apps}阅读 Mastodon 上的消息。",
+  "home.column_settings.advanced": "高端",
+  "home.column_settings.basic": "基本",
+  "home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤",
+  "home.column_settings.show_reblogs": "显示被转的嘟文",
+  "home.column_settings.show_replies": "显示回应嘟文",
+  "home.settings": "字段设置",
+  "lightbox.close": "关闭",
+  "loading_indicator.label": "加载中...",
+  "media_gallery.toggle_visible": "打开或关上",
+  "missing_indicator.label": "找不到内容",
+  "navigation_bar.blocks": "被屏蔽的用户",
+  "navigation_bar.community_timeline": "本站时间轴",
+  "navigation_bar.edit_profile": "修改个人资料",
+  "navigation_bar.favourites": "赞的内容",
+  "navigation_bar.follow_requests": "关注请求",
+  "navigation_bar.info": "关于本服务站",
+  "navigation_bar.logout": "注销",
+  // intentional departure from https://github.com/tootsuite/mastodon/blob/f864fee1/config/locales/zh-CN.yml#L126:
+  // clashes for settings/preferences
+  "navigation_bar.preferences": "首选项",
+  "navigation_bar.public_timeline": "跨站公共时间轴",
+  "notification.favourite": "{name} 赞你的嘟文",
+  "notification.follow": "{name} 开始关注你",
+  "notification.mention": "{name} 提及你",
+  "notification.reblog": "{name} 转嘟你的嘟文",
+  "notifications.clear_confirmation": "你确定要清空通知纪录吗?",
+  "notifications.clear": "清空通知纪录",
+  "notifications.column_settings.alert": "显示桌面通知",
+  "notifications.column_settings.favourite": "赞你的嘟文:",
+  "notifications.column_settings.follow": "关注你:",
+  "notifications.column_settings.mention": "提及你:",
+  "notifications.column_settings.reblog": "转你的嘟文:",
+  "notifications.column_settings.show": "在通知栏显示",
+  "notifications.column_settings.sound": "播放音效",
+  "notifications.settings": "字段设置",
+  "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": "公开",
+  "reply_indicator.cancel": "取消",
+  "report.heading": "举报",
+  "report.placeholder": "额外消息",
+  "report.submit": "提交",
+  "report.target": "Reporting",
+  "search_results.total": "{count} 项结果",
+  "search.account": "用户",
+  "search.hashtag": "标签",
+  "search.placeholder": "搜索",
+  "search.status_by": "按{name}搜索嘟文",
+  "status.delete": "删除",
+  "status.favourite": "赞",
+  "status.load_more": "加载更多",
+  "status.media_hidden": "隐藏媒体内容",
+  "status.mention": "提及 @{name}",
+  "status.open": "展开嘟文",
+  "status.reblog": "转嘟",
+  "status.reblogged_by": "{name} 转嘟",
+  "status.reply": "回应",
+  "status.report": "举报 @{name}",
+  "status.sensitive_toggle": "点击显示",
+  "status.sensitive_warning": "敏感内容",
+  "status.show_less": "减少显示",
+  "status.show_more": "显示更多",
+  "tabs_bar.compose": "撰写",
+  "tabs_bar.federated_timeline": "跨站",
+  "tabs_bar.home": "主页",
+  "tabs_bar.local_timeline": "本站",
+  "tabs_bar.mentions": "提及",
+  "tabs_bar.notifications": "通知",
+  "tabs_bar.public": "跨站公共时间轴",
+  "upload_area.title": "将文件拖放至此上传",
+  "upload_button.label": "上传媒体文件",
+  "upload_form.undo": "还原",
+  "upload_progress.label": "上传中……",
+  "video_player.expand": "展开影片",
+  "video_player.toggle_sound": "开关音效",
+  "video_player.toggle_visible": "打开或关上",
+};
+
+export default zh_cn;
diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss
index e27b88e5f..2916b915b 100644
--- a/app/assets/stylesheets/admin.scss
+++ b/app/assets/stylesheets/admin.scss
@@ -120,10 +120,12 @@
   @media screen and (max-width: 600px) {
     display: block;
     overflow-y: auto;
+    -webkit-overflow-scrolling: touch;
 
     .sidebar-wrapper, .content-wrapper {
       flex: 0 0 auto;
       height: auto;
+      overflow: initial;
     }
 
     .sidebar {
diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss
index 84344e94d..6f407a6d5 100644
--- a/app/assets/stylesheets/components.scss
+++ b/app/assets/stylesheets/components.scss
@@ -2050,8 +2050,8 @@ button.icon-button.active i.fa-retweet {
 .onboarding-modal__pager {
   height: 80vh;
   width: 80vw;
-  max-width: 500px;
-  max-height: 350px;
+  max-width: 520px;
+  max-height: 420px;
   position: relative;
 
   & > div {
@@ -2281,6 +2281,7 @@ button.icon-button.active i.fa-retweet {
 }
 
 .boost-modal__container {
+  overflow-x: scroll;
   padding: 10px;
 
   .status {
diff --git a/app/assets/stylesheets/forms.scss b/app/assets/stylesheets/forms.scss
index e5e8697a0..c6a8b5b02 100644
--- a/app/assets/stylesheets/forms.scss
+++ b/app/assets/stylesheets/forms.scss
@@ -42,7 +42,7 @@ code {
     }
   }
 
-  .input.file, .input.select {
+  .input.file, .input.select, .input.radio_buttons {
     padding: 15px 0;
     margin-bottom: 0;
 
@@ -59,6 +59,15 @@ code {
     margin-bottom: 25px;
   }
 
+  .input.radio_buttons .radio label {
+    margin-bottom: 5px;
+    font-family: inherit;
+    font-size: 14px;
+    color: white;
+    display: block;
+    width: auto;
+  }
+
   .input.boolean {
     margin-bottom: 5px;
 
@@ -72,7 +81,8 @@ code {
 
     label.checkbox {
       position: relative;
-	    padding-left: 25px;
+      padding-left: 25px;
+      flex: 1 1 auto;
     }
 
     input[type=checkbox] {
@@ -182,6 +192,10 @@ code {
       }
     }
   }
+
+  select {
+    font-size: 16px;
+  }
 }
 
 .flash-message {
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 1976ce330..b0e26918e 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -77,9 +77,9 @@ class Api::V1::StatusesController < ApiController
   end
 
   def unreblog
-    reblog         = Status.where(account_id: current_user.account, reblog_of_id: params[:id]).first!
-    @status        = reblog.reblog
-    @reblogged_map = { @status.id => false }
+    reblog       = Status.where(account_id: current_user.account, reblog_of_id: params[:id]).first!
+    @status      = reblog.reblog
+    @reblogs_map = { @status.id => false }
 
     RemovalWorker.perform_async(reblog.id)
 
@@ -93,7 +93,7 @@ class Api::V1::StatusesController < ApiController
 
   def unfavourite
     @status         = Status.find(params[:id])
-    @favourited_map = { @status.id => false }
+    @favourites_map = { @status.id => false }
 
     UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
 
diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb
index 57604f1dc..478d21bc7 100644
--- a/app/controllers/api_controller.rb
+++ b/app/controllers/api_controller.rb
@@ -100,17 +100,4 @@ class ApiController < ApplicationController
     @reblogs_map    = Status.reblogs_map(status_ids, current_account)
     @favourites_map = Status.favourites_map(status_ids, current_account)
   end
-
-  def set_counters_maps(statuses) # rubocop:disable Style/AccessorMethodName
-    status_ids             = statuses.compact.map { |s| s.reblog? ? s.reblog_of_id : s.id }.uniq
-    @favourites_counts_map = Favourite.select('status_id, COUNT(id) AS favourites_count').group('status_id').where(status_id: status_ids).map { |f| [f.status_id, f.favourites_count] }.to_h
-    @reblogs_counts_map    = Status.select('statuses.id, COUNT(reblogs.id) AS reblogs_count').joins('LEFT OUTER JOIN statuses AS reblogs ON statuses.id = reblogs.reblog_of_id').where(id: status_ids).group('statuses.id').map { |r| [r.id, r.reblogs_count] }.to_h
-  end
-
-  def set_account_counters_maps(accounts) # rubocop:disable Style/AccessorMethodName
-    account_ids = accounts.compact.map(&:id).uniq
-    @followers_counts_map = Follow.unscoped.select('target_account_id, COUNT(account_id) AS followers_count').group('target_account_id').where(target_account_id: account_ids).map { |f| [f.target_account_id, f.followers_count] }.to_h
-    @following_counts_map = Follow.unscoped.select('account_id, COUNT(target_account_id) AS following_count').group('account_id').where(account_id: account_ids).map { |f| [f.account_id, f.following_count] }.to_h
-    @statuses_counts_map  = Status.unscoped.select('account_id, COUNT(id) AS statuses_count').group('account_id').where(account_id: account_ids).map { |s| [s.account_id, s.statuses_count] }.to_h
-  end
 end
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index f8050afb5..dd30be32a 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -10,6 +10,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
 
   def build_resource(hash = nil)
     super(hash)
+    resource.locale = I18n.locale
     resource.build_account if resource.account.nil?
   end
 
diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb
index 488c4f944..fa1daf012 100644
--- a/app/controllers/media_controller.rb
+++ b/app/controllers/media_controller.rb
@@ -1,16 +1,19 @@
 # frozen_string_literal: true
 
 class MediaController < ApplicationController
-  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
 
   private
 
-  def set_media_attachment
-    @media_attachment = MediaAttachment.where.not(status_id: nil).find_by!(shortcode: params[:id])
-    raise ActiveRecord::RecordNotFound unless @media_attachment.status.permitted?(current_account)
+  def media_attachment
+    MediaAttachment.attached.find_by!(shortcode: params[:id])
+  end
+
+  def verify_permitted_status
+    raise ActiveRecord::RecordNotFound unless media_attachment.status.permitted?(current_account)
   end
 end
diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb
index 1a8ef5f90..4a521d102 100644
--- a/app/controllers/well_known/webfinger_controller.rb
+++ b/app/controllers/well_known/webfinger_controller.rb
@@ -8,8 +8,13 @@ module WellKnown
       @magic_key = pem_to_magic_key(@account.keypair.public_key)
 
       respond_to do |format|
-        format.xml  { render content_type: 'application/xrd+xml' }
-        format.json { render content_type: 'application/jrd+json' }
+        format.any(:json, :html) do
+          render formats: :json, content_type: 'application/jrd+json'
+        end
+
+        format.xml do
+          render content_type: 'application/xrd+xml'
+        end
       end
     rescue ActiveRecord::RecordNotFound
       head 404
diff --git a/app/helpers/instance_helper.rb b/app/helpers/instance_helper.rb
new file mode 100644
index 000000000..a1c3c3521
--- /dev/null
+++ b/app/helpers/instance_helper.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module InstanceHelper
+  def site_title
+    Setting.site_title.to_s
+  end
+
+  def site_hostname
+    Rails.configuration.x.local_domain
+  end
+end
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index cf7b9b381..01900b87f 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -7,17 +7,20 @@ module SettingsHelper
     es: 'Español',
     eo: 'Esperanto',
     fr: 'Français',
-    it: 'Italiano',
+    hr: 'Hrvatski',
     hu: 'Magyar',
+    it: 'Italiano',
     nl: 'Nederlands',
     no: 'Norsk',
     pt: 'Português',
+    'pt-BR': 'Português do Brasil',
     fi: 'Suomi',
     ru: 'Русский',
     uk: 'Українська',
     ja: '日本語',
     'zh-CN': '简体中文',
     'zh-HK': '繁體中文(香港)',
+    'zh-TW': '繁體中文(臺灣)',
     bg: 'Български',
   }.freeze
 
diff --git a/app/helpers/site_title_helper.rb b/app/helpers/site_title_helper.rb
deleted file mode 100644
index d2caa9203..000000000
--- a/app/helpers/site_title_helper.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-module SiteTitleHelper
-  def site_title
-    Setting.site_title.to_s
-  end
-end
diff --git a/app/lib/atom_serializer.rb b/app/lib/atom_serializer.rb
index 4e4031bba..5aeb7b4f9 100644
--- a/app/lib/atom_serializer.rb
+++ b/app/lib/atom_serializer.rb
@@ -3,13 +3,11 @@
 class AtomSerializer
   include RoutingHelper
 
-  INVALID_XML_CHARS = /[^\u0009\u000a\u000d\u0020-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]/
-
   class << self
     def render(element)
       document = Ox::Document.new(version: '1.0')
       document << element
-      ('<?xml version="1.0"?>' + Ox.dump(element)).force_encoding('UTF-8')
+      ('<?xml version="1.0"?>' + Ox.dump(element, effort: :tolerant)).force_encoding('UTF-8')
     end
   end
 
@@ -319,7 +317,7 @@ class AtomSerializer
   end
 
   def sanitize_str(raw_str)
-    raw_str.to_s.gsub(INVALID_XML_CHARS, '')
+    raw_str.to_s
   end
 
   def add_namespaces(parent)
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index 0d9f10a08..e5dbfeeda 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -3,4 +3,5 @@
 class ApplicationMailer < ActionMailer::Base
   default from: ENV.fetch('SMTP_FROM_ADDRESS') { 'notifications@localhost' }
   layout 'mailer'
+  helper :instance
 end
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 64ca92a3a..6abf9c9ca 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -4,6 +4,8 @@ class UserMailer < Devise::Mailer
   default from: ENV.fetch('SMTP_FROM_ADDRESS') { 'notifications@localhost' }
   layout 'mailer'
 
+  helper :instance
+
   def confirmation_instructions(user, token, _opts = {})
     @resource = user
     @token    = token
diff --git a/app/models/account.rb b/app/models/account.rb
index 8ceda7f97..259a87451 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -108,10 +108,6 @@ class Account < ApplicationRecord
     follow_requests.where(target_account: other_account).exists?
   end
 
-  def followers_domains
-    followers.reorder('').select('DISTINCT accounts.domain').map(&:domain)
-  end
-
   def local?
     domain.nil?
   end
@@ -231,18 +227,20 @@ class Account < ApplicationRecord
         WITH first_degree AS (
             SELECT target_account_id
             FROM follows
-            WHERE account_id = ?
+            WHERE account_id = :account_id
           )
         SELECT accounts.*
         FROM follows
         INNER JOIN accounts ON follows.target_account_id = accounts.id
-        WHERE account_id IN (SELECT * FROM first_degree) AND target_account_id NOT IN (SELECT * FROM first_degree) AND target_account_id <> ?
+        WHERE account_id IN (SELECT * FROM first_degree) AND target_account_id NOT IN (SELECT * FROM first_degree) AND target_account_id <> :account_id
         GROUP BY target_account_id, accounts.id
         ORDER BY count(account_id) DESC
-        LIMIT ?
+        LIMIT :limit
       SQL
 
-      Account.find_by_sql([sql, account.id, account.id, limit])
+      find_by_sql(
+        [sql, { account_id: account.id, limit: limit }]
+      )
     end
 
     def search_for(terms, limit = 10)
diff --git a/app/models/block.rb b/app/models/block.rb
index ae456a6b6..c978b2200 100644
--- a/app/models/block.rb
+++ b/app/models/block.rb
@@ -7,4 +7,14 @@ class Block < ApplicationRecord
   belongs_to :target_account, class_name: 'Account', required: true
 
   validates :account_id, uniqueness: { scope: :target_account_id }
+
+  after_create  :remove_blocking_cache
+  after_destroy :remove_blocking_cache
+
+  private
+
+  def remove_blocking_cache
+    Rails.cache.delete("exclude_account_ids_for:#{account_id}")
+    Rails.cache.delete("exclude_account_ids_for:#{target_account_id}")
+  end
 end
diff --git a/app/models/favourite.rb b/app/models/favourite.rb
index 41d06e734..32d54476b 100644
--- a/app/models/favourite.rb
+++ b/app/models/favourite.rb
@@ -3,14 +3,14 @@
 class Favourite < ApplicationRecord
   include Paginable
 
-  belongs_to :account, inverse_of: :favourites
-  belongs_to :status,  inverse_of: :favourites, counter_cache: true
+  belongs_to :account, inverse_of: :favourites, required: true
+  belongs_to :status,  inverse_of: :favourites, counter_cache: true, required: true
 
   has_one :notification, as: :activity, dependent: :destroy
 
   validates :status_id, uniqueness: { scope: :account_id }
 
   before_validation do
-    self.status = status.reblog if status.reblog?
+    self.status = status.reblog if status&.reblog?
   end
 end
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 818190214..85e82e12b 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -33,6 +33,7 @@ class MediaAttachment < ApplicationRecord
 
   validates :account, presence: true
 
+  scope :attached, -> { where.not(status_id: nil) }
   scope :local, -> { where(remote_url: '') }
   default_scope { order('id asc') }
 
diff --git a/app/models/mute.rb b/app/models/mute.rb
index a5b334c85..d0de62ed5 100644
--- a/app/models/mute.rb
+++ b/app/models/mute.rb
@@ -3,9 +3,17 @@
 class Mute < ApplicationRecord
   include Paginable
 
-  belongs_to :account
-  belongs_to :target_account, class_name: 'Account'
+  belongs_to :account, required: true
+  belongs_to :target_account, class_name: 'Account', required: true
 
-  validates :account, :target_account, presence: true
   validates :account_id, uniqueness: { scope: :target_account_id }
+
+  after_create  :remove_blocking_cache
+  after_destroy :remove_blocking_cache
+
+  private
+
+  def remove_blocking_cache
+    Rails.cache.delete("exclude_account_ids_for:#{account_id}")
+  end
 end
diff --git a/app/models/status.rb b/app/models/status.rb
index c0a5d9d1b..a9b7327c3 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -10,7 +10,7 @@ class Status < ApplicationRecord
 
   belongs_to :application, class_name: 'Doorkeeper::Application'
 
-  belongs_to :account, inverse_of: :statuses, counter_cache: true
+  belongs_to :account, inverse_of: :statuses, counter_cache: true, required: true
   belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account'
 
   belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies
@@ -26,7 +26,6 @@ class Status < ApplicationRecord
   has_one :notification, as: :activity, dependent: :destroy
   has_one :preview_card, dependent: :destroy
 
-  validates :account, presence: true
   validates :uri, uniqueness: true, unless: 'local?'
   validates :text, presence: true, unless: 'reblog?'
   validates_with StatusLengthValidator
@@ -184,7 +183,7 @@ class Status < ApplicationRecord
     private
 
     def filter_timeline(query, account)
-      blocked = Block.where(account: account).pluck(:target_account_id) + Block.where(target_account: account).pluck(:account_id) + Mute.where(account: account).pluck(:target_account_id)
+      blocked = Rails.cache.fetch("exclude_account_ids_for:#{account.id}") { Block.where(account: account).pluck(:target_account_id) + Block.where(target_account: account).pluck(:account_id) + Mute.where(account: account).pluck(:target_account_id) }
       query   = query.where('statuses.account_id NOT IN (?)', blocked) unless blocked.empty?  # Only give us statuses from people we haven't blocked, or muted, or that have blocked us
       query   = query.where('accounts.silenced = TRUE') if account.silenced?                  # and if we're hellbanned, only people who are also hellbanned
       query
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index 497cabb09..63553e9fe 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -4,7 +4,7 @@ class Subscription < ApplicationRecord
   MIN_EXPIRATION = 3600 * 24 * 7
   MAX_EXPIRATION = 3600 * 24 * 30
 
-  belongs_to :account
+  belongs_to :account, required: true
 
   validates :callback_url, presence: true
   validates :callback_url, uniqueness: { scope: :account_id }
diff --git a/app/models/user.rb b/app/models/user.rb
index d50101baf..cd1f816ca 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -9,14 +9,12 @@ class User < ApplicationRecord
          otp_secret_encryption_key: ENV['OTP_SECRET'],
          otp_number_of_backup_codes: 10
 
-  belongs_to :account, inverse_of: :user
+  belongs_to :account, inverse_of: :user, required: true
   accepts_nested_attributes_for :account
 
-  validates :account, presence: true
   validates :locale, inclusion: I18n.available_locales.map(&:to_s), unless: 'locale.nil?'
   validates :email, email: true
 
-  scope :prolific,  -> { joins('inner join statuses on statuses.account_id = users.account_id').select('users.*, count(statuses.id) as statuses_count').group('users.id').order('statuses_count desc') }
   scope :recent,    -> { order('id desc') }
   scope :admins,    -> { where(admin: true) }
   scope :confirmed, -> { where.not(confirmed_at: nil) }
diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb
index a9cb85500..58a23d978 100644
--- a/app/services/account_search_service.rb
+++ b/app/services/account_search_service.rb
@@ -41,7 +41,7 @@ class AccountSearchService < BaseService
   end
 
   def query_username
-    @_query_username ||= split_query_string.first
+    @_query_username ||= split_query_string.first || ''
   end
 
   def query_domain
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index ec2b90e07..00af28edd 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -36,7 +36,7 @@ class PostStatusService < BaseService
   private
 
   def validate_media!(media_ids)
-    return if media_ids.nil? || !media_ids.is_a?(Enumerable)
+    return if media_ids.blank? || !media_ids.is_a?(Enumerable)
 
     raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if media_ids.size > 4
 
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index 418c98247..84b29912c 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -1,11 +1,11 @@
 - content_for :page_title do
-  #{Rails.configuration.x.local_domain}
+  = site_hostname
 
 .wrapper.thicc
   .sidebar-layout
     .main
       .panel
-        %h2= Rails.configuration.x.local_domain
+        %h2= site_hostname
 
         - unless @instance_presenter.site_description.blank?
           %p= @instance_presenter.site_description.html_safe
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index 39686b531..49ad03557 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -2,13 +2,13 @@
   = javascript_include_tag 'application_public', integrity: true
 
 - content_for :page_title do
-  = Rails.configuration.x.local_domain
+  = site_hostname
 
 - content_for :header_tags do
   %meta{ property: 'og:site_name', content: site_title }/
   %meta{ property: 'og:type', content: 'website' }/
-  %meta{ property: 'og:title', content: Rails.configuration.x.local_domain }/
-  %meta{ property: 'og:description', content: strip_tags(@instance_presenter.site_description.blank? ? t('about.about_mastodon') : @instance_presenter.site_description) }/
+  %meta{ property: 'og:title', content: site_hostname }/
+  %meta{ property: 'og:description', content: strip_tags(@instance_presenter.site_description.presence || t('about.about_mastodon')) }/
   %meta{ property: 'og:image', content: asset_url('mastodon_small.jpg') }/
   %meta{ property: 'og:image:width', content: '400' }/
   %meta{ property: 'og:image:height', content: '400' }/
@@ -72,7 +72,7 @@
           = t 'about.features.api'
 
   - unless @instance_presenter.site_description.blank?
-    %h3= t('about.description_headline', domain: Rails.configuration.x.local_domain)
+    %h3= t('about.description_headline', domain: site_hostname)
     %p= @instance_presenter.site_description.html_safe
 
   .actions
diff --git a/app/views/about/terms.en.html.haml b/app/views/about/terms.en.html.haml
index e1766ca16..7e0fb94c2 100644
--- a/app/views/about/terms.en.html.haml
+++ b/app/views/about/terms.en.html.haml
@@ -1,5 +1,5 @@
 - content_for :page_title do
-  #{Rails.configuration.x.local_domain} Terms of Service and Privacy Policy
+  #{site_hostname} Terms of Service and Privacy Policy
 
 .wrapper
   %h2 Privacy Policy
diff --git a/app/views/about/terms.no.html.haml b/app/views/about/terms.no.html.haml
index 32ec57ed1..46f62950d 100644
--- a/app/views/about/terms.no.html.haml
+++ b/app/views/about/terms.no.html.haml
@@ -1,5 +1,5 @@
 - content_for :page_title do
-  #{Rails.configuration.x.local_domain} Personvern og villkår for bruk av nettstedet
+  #{site_hostname} Personvern og villkår for bruk av nettstedet
 
 .wrapper
   %h2 Personvernserklæring
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 9a70fd16f..b01f3c4e3 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -7,7 +7,7 @@
 
   %meta{ property: 'og:site_name', content: site_title }/
   %meta{ property: 'og:type', content: 'profile' }/
-  %meta{ property: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
+  %meta{ property: 'og:title', content: "#{@account.username} on #{site_hostname}" }/
   %meta{ property: 'og:description', content: @account.note }/
   %meta{ property: 'og:image', content: full_asset_url(@account.avatar.url(:original)) }/
   %meta{ property: 'og:image:width', content: '120' }/
@@ -18,7 +18,7 @@
   = render partial: 'shared/landing_strip', locals: { account: @account }
 
 .h-feed
-  %data.p-name{ value: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
+  %data.p-name{ value: "#{@account.username} on #{site_hostname}" }/
 
   = render 'header', account: @account
 
diff --git a/app/views/api/oembed/show.json.rabl b/app/views/api/oembed/show.json.rabl
index 311c02dad..11dcec538 100644
--- a/app/views/api/oembed/show.json.rabl
+++ b/app/views/api/oembed/show.json.rabl
@@ -6,7 +6,7 @@ node(:version) { '1.0' }
 node(:title, &:title)
 node(:author_name) { |entry| entry.account.display_name.blank? ? entry.account.username : entry.account.display_name }
 node(:author_url) { |entry| account_url(entry.account) }
-node(:provider_name) { Rails.configuration.x.local_domain }
+node(:provider_name) { site_hostname }
 node(:provider_url) { root_url }
 node(:cache_age) { 86_400 }
 node(:html) { |entry| "<iframe src=\"#{embed_account_stream_entry_url(entry.account, entry)}\" style=\"width: 100%; overflow: hidden\" frameborder=\"0\" width=\"#{@width}\" height=\"#{@height}\" scrolling=\"no\"></iframe>" }
diff --git a/app/views/api/v1/instances/show.rabl b/app/views/api/v1/instances/show.rabl
index 88eb08a9e..f5598fde3 100644
--- a/app/views/api/v1/instances/show.rabl
+++ b/app/views/api/v1/instances/show.rabl
@@ -1,6 +1,6 @@
 object false
 
-node(:uri)         { Rails.configuration.x.local_domain }
+node(:uri)         { site_hostname }
 node(:title)       { Setting.site_title }
 node(:description) { Setting.site_description }
 node(:email)       { Setting.site_contact_email }
diff --git a/app/views/api/v1/statuses/_media.rabl b/app/views/api/v1/statuses/_media.rabl
index 80d80ea05..2f56c6d07 100644
--- a/app/views/api/v1/statuses/_media.rabl
+++ b/app/views/api/v1/statuses/_media.rabl
@@ -1,5 +1,5 @@
 attributes :id, :remote_url, :type
 
-node(:url)         { |media| media.file.blank? ? media.remote_url : full_asset_url(media.file.url(:original)) }
-node(:preview_url) { |media| media.file.blank? ? media.remote_url : full_asset_url(media.file.url(:small)) }
+node(:url)         { |media| full_asset_url(media.file.url(:original)) }
+node(:preview_url) { |media| full_asset_url(media.file.url(:small)) }
 node(:text_url)    { |media| media.local? ? medium_url(media) : nil }
diff --git a/app/views/home/initial_state.json.rabl b/app/views/home/initial_state.json.rabl
index a2ab2d060..b599b5cf0 100644
--- a/app/views/home/initial_state.json.rabl
+++ b/app/views/home/initial_state.json.rabl
@@ -5,7 +5,7 @@ node(:meta) do
     streaming_api_base_url: @streaming_api_base_url,
     access_token: @token,
     locale: I18n.locale,
-    domain: Rails.configuration.x.local_domain,
+    domain: site_hostname,
     me: current_account.id,
     admin: @admin.try(:id),
     boost_modal: current_account.user.setting_boost_modal,
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index a27c3de95..688deaebd 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -6,6 +6,7 @@
     %meta{'http-equiv' => 'X-UA-Compatible', :content => 'IE=edge'}/
 
     %link{:rel => "apple-touch-icon", :sizes => "180x180", :href => "/apple-touch-icon.png"}/
+    %link{:rel => "mask-icon", :href => "/mask-icon.svg", :color => "#2B90D9"}/
     %link{:rel => "manifest", :href => "/manifest.json"}/
     %meta{:name => "msapplication-config", :content => "/browserconfig.xml"}/
     %meta{:name => "theme-color", :content => "#282c37"}/
@@ -13,7 +14,7 @@
 
     %title<
       - if content_for?(:page_title)
-        = yield(:page_title)
+        = yield(:page_title).strip
         = ' - '
       = site_title
 
diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb
index 21bf444c3..cdb284de8 100644
--- a/app/views/layouts/mailer.text.erb
+++ b/app/views/layouts/mailer.text.erb
@@ -1,5 +1,5 @@
 <%= yield %>
 ---
 
-<%= t('application_mailer.signature', instance: Rails.configuration.x.local_domain) %>
+<%= t('application_mailer.signature', instance: site_hostname) %>
 <%= t('application_mailer.settings', link: settings_preferences_url) %>
diff --git a/app/views/layouts/public.html.haml b/app/views/layouts/public.html.haml
index fdde0a681..556102f53 100644
--- a/app/views/layouts/public.html.haml
+++ b/app/views/layouts/public.html.haml
@@ -4,7 +4,7 @@
 - content_for :content do
   .container= yield
   .footer
-    %span.domain= link_to Rails.configuration.x.local_domain, root_path
+    %span.domain= link_to site_hostname, root_path
     %span.powered-by
       = t('generic.powered_by', link: link_to('Mastodon', 'https://github.com/tootsuite/mastodon')).html_safe
 
diff --git a/app/views/settings/imports/show.html.haml b/app/views/settings/imports/show.html.haml
index 8502913dc..991dd4e94 100644
--- a/app/views/settings/imports/show.html.haml
+++ b/app/views/settings/imports/show.html.haml
@@ -4,7 +4,7 @@
 %p.hint= t('imports.preface')
 
 = simple_form_for @import, url: settings_import_path do |f|
-  = f.input :type, collection: Import.types.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| I18n.t("imports.types.#{type}") }
+  = f.input :type, collection: Import.types.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| I18n.t("imports.types.#{type}") }, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
   = f.input :data, wrapper: :with_label, hint: t('simple_form.hints.imports.data')
 
   .actions
diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml
index 3fdcca041..ce3929629 100644
--- a/app/views/settings/preferences/show.html.haml
+++ b/app/views/settings/preferences/show.html.haml
@@ -7,7 +7,7 @@
   .fields-group
     = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| human_locale(locale) }
 
-    = f.input :setting_default_privacy, collection: Status.visibilities.keys - ['direct'], wrapper: :with_label, include_blank: false, label_method: lambda { |visibility| I18n.t("statuses.visibilities.#{visibility}") }, required: false
+    = f.input :setting_default_privacy, collection: Status.visibilities.keys - ['direct'], wrapper: :with_label, include_blank: false, label_method: lambda { |visibility| I18n.t("statuses.visibilities.#{visibility}") }, required: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
 
   .fields-group
     = f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff|
diff --git a/app/views/shared/_landing_strip.html.haml b/app/views/shared/_landing_strip.html.haml
index 3536c5ca8..02e694418 100644
--- a/app/views/shared/_landing_strip.html.haml
+++ b/app/views/shared/_landing_strip.html.haml
@@ -1,5 +1,5 @@
 .landing-strip
   = t('landing_strip_html',
     name: content_tag(:span, display_name(account), class: :emojify),
-    domain: Rails.configuration.x.local_domain,
+    domain: site_hostname,
     sign_up_path: new_user_registration_path)
diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml
index dea5e9d40..31efa26c4 100644
--- a/app/views/stream_entries/show.html.haml
+++ b/app/views/stream_entries/show.html.haml
@@ -4,7 +4,7 @@
 
   %meta{ property: 'og:site_name', content: site_title }/
   %meta{ property: 'og:type', content: 'article' }/
-  %meta{ property: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
+  %meta{ property: 'og:title', content: "#{@account.username} on #{site_hostname}" }/
 
   = render 'stream_entries/og_description', activity: @stream_entry.activity
   = render 'stream_entries/og_image', activity: @stream_entry.activity, account: @account
diff --git a/app/views/user_mailer/confirmation_instructions.ja.html.erb b/app/views/user_mailer/confirmation_instructions.ja.html.erb
index bbb44b2cc..1232f94b4 100644
--- a/app/views/user_mailer/confirmation_instructions.ja.html.erb
+++ b/app/views/user_mailer/confirmation_instructions.ja.html.erb
@@ -1,5 +1,11 @@
 <p>ようこそ<%= @resource.email %>さん</p>
 
-<p>以下のリンクをクリックしてMastodonアカウントのメールアドレスを確認してください</p>
+<p><%= @instance %>にアカウントが作成されました。</p>
+
+<p>以下のリンクをクリックしてMastodonアカウントのメールアドレスを確認してください。</p>
 
 <p><%= link_to 'メールアドレスの確認', confirmation_url(@resource, confirmation_token: @token) %></p>
+
+<p>また、インスタンスの<%= link_to '利用規約', terms_url %>についてもご確認ください。</p>
+
+<p><%= @instance %> チーム</p>
diff --git a/app/views/user_mailer/confirmation_instructions.ja.text.erb b/app/views/user_mailer/confirmation_instructions.ja.text.erb
index ad8abee2d..99868ba8a 100644
--- a/app/views/user_mailer/confirmation_instructions.ja.text.erb
+++ b/app/views/user_mailer/confirmation_instructions.ja.text.erb
@@ -1,5 +1,11 @@
 ようこそ<%= @resource.email %>さん
 
-以下のリンクをクリックしてMastodonアカウントのメールアドレスを確認してください
+<%= @instance %>にアカウントが作成されました。
+
+以下のリンクをクリックしてMastodonアカウントのメールアドレスを確認してください。
 
 <%= confirmation_url(@resource, confirmation_token: @token) %>
+
+また、インスタンスの<%= link_to '利用規約', terms_url %>についてもご確認ください。
+
+<%= @instance %> チーム
diff --git a/app/views/user_mailer/reset_password_instructions.ja.html.erb b/app/views/user_mailer/reset_password_instructions.ja.html.erb
index 156758ef5..d0d7203f4 100644
--- a/app/views/user_mailer/reset_password_instructions.ja.html.erb
+++ b/app/views/user_mailer/reset_password_instructions.ja.html.erb
@@ -4,5 +4,5 @@
 
 <p><%= link_to 'パスワードを変更', edit_password_url(@resource, reset_password_token: @token) %></p>
 
-<p>このメールに見に覚えのない場合は無視してください。</p>
+<p>このメールに身に覚えのない場合は無視してください。</p>
 <p>上記のリンクにアクセスし、変更をしない限りパスワードは変更されません。</p>
diff --git a/app/views/user_mailer/reset_password_instructions.ja.text.erb b/app/views/user_mailer/reset_password_instructions.ja.text.erb
index 5fb0eba04..9ed607b58 100644
--- a/app/views/user_mailer/reset_password_instructions.ja.text.erb
+++ b/app/views/user_mailer/reset_password_instructions.ja.text.erb
@@ -4,5 +4,5 @@ Mastodonアカウントのパスワードの変更がリクエストされまし
 
 <%= edit_password_url(@resource, reset_password_token: @token) %>
 
-このメールに見に覚えのない場合は無視してください。
+このメールに身に覚えのない場合は無視してください。
 上記のリンクにアクセスし、変更をしない限りパスワードは変更されません。