about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/api/web/embeds_controller.rb16
-rw-r--r--app/controllers/tags_controller.rb10
-rw-r--r--app/javascript/flavours/glitch/components/status_action_bar.js2
-rw-r--r--app/javascript/flavours/glitch/features/status/components/action_bar.js5
-rw-r--r--app/javascript/flavours/glitch/styles/components/announcements.scss7
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js2
-rw-r--r--app/javascript/mastodon/features/status/components/action_bar.js6
-rw-r--r--app/javascript/mastodon/locales/ar.json6
-rw-r--r--app/javascript/mastodon/locales/ast.json58
-rw-r--r--app/javascript/mastodon/locales/ca.json4
-rw-r--r--app/javascript/mastodon/locales/cy.json6
-rw-r--r--app/javascript/mastodon/locales/gl.json24
-rw-r--r--app/javascript/mastodon/locales/id.json2
-rw-r--r--app/javascript/mastodon/locales/kab.json18
-rw-r--r--app/javascript/mastodon/locales/pl.json18
-rw-r--r--app/javascript/mastodon/locales/sk.json6
-rw-r--r--app/javascript/mastodon/locales/ta.json102
-rw-r--r--app/javascript/styles/mastodon/components.scss7
-rw-r--r--app/lib/formatter.rb2
-rw-r--r--app/lib/sanitize_config.rb39
20 files changed, 190 insertions, 150 deletions
diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb
index 6231733b7..4aa31695c 100644
--- a/app/controllers/api/web/embeds_controller.rb
+++ b/app/controllers/api/web/embeds_controller.rb
@@ -7,15 +7,21 @@ class Api::Web::EmbedsController < Api::Web::BaseController
 
   def create
     status = StatusFinder.new(params[:url]).status
+
+    return not_found if status.hidden?
+
     render json: status, serializer: OEmbedSerializer, width: 400
   rescue ActiveRecord::RecordNotFound
     oembed = FetchOEmbedService.new.call(params[:url])
-    oembed[:html] = Formatter.instance.sanitize(oembed[:html], Sanitize::Config::MASTODON_OEMBED) if oembed[:html].present?
 
-    if oembed
-      render json: oembed
-    else
-      render json: {}, status: :not_found
+    return not_found if oembed.nil?
+
+    begin
+      oembed[:html] = Formatter.instance.sanitize(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
+    rescue ArgumentError
+      return not_found
     end
+
+    render json: oembed
   end
 end
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index ef61c980f..c59446efc 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -25,7 +25,7 @@ class TagsController < ApplicationController
       format.rss do
         expires_in 0, public: true
 
-        @statuses = HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none)).limit(PAGE_SIZE)
+        @statuses = HashtagQueryService.new.call(@tag, filter_params).limit(PAGE_SIZE)
         @statuses = cache_collection(@statuses, Status)
 
         render xml: RSS::TagSerializer.render(@tag, @statuses)
@@ -34,7 +34,7 @@ class TagsController < ApplicationController
       format.json do
         expires_in 3.minutes, public: public_fetch_mode?
 
-        @statuses = HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none), current_account, params[:local]).paginate_by_max_id(PAGE_SIZE, params[:max_id])
+        @statuses = HashtagQueryService.new.call(@tag, filter_params, current_account, params[:local]).paginate_by_max_id(PAGE_SIZE, params[:max_id])
         @statuses = cache_collection(@statuses, Status)
 
         render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
@@ -58,10 +58,14 @@ class TagsController < ApplicationController
 
   def collection_presenter
     ActivityPub::CollectionPresenter.new(
-      id: tag_url(@tag, params.slice(:any, :all, :none)),
+      id: tag_url(@tag, filter_params),
       type: :ordered,
       size: @tag.statuses.count,
       items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }
     )
   end
+
+  def filter_params
+    params.slice(:any, :all, :none).permit(:any, :all, :none)
+  end
 end
diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js
index 60b370622..0a481c816 100644
--- a/app/javascript/flavours/glitch/components/status_action_bar.js
+++ b/app/javascript/flavours/glitch/components/status_action_bar.js
@@ -265,7 +265,7 @@ class StatusActionBar extends ImmutablePureComponent {
       replyTitle = intl.formatMessage(messages.replyAll);
     }
 
-    const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
+    const shareButton = ('share' in navigator) && publicStatus && (
       <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
     );
 
diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.js b/app/javascript/flavours/glitch/features/status/components/action_bar.js
index d71a3ae08..bb53f9288 100644
--- a/app/javascript/flavours/glitch/features/status/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/status/components/action_bar.js
@@ -188,7 +188,7 @@ class ActionBar extends React.PureComponent {
       }
     }
 
-    const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
+    const shareButton = ('share' in navigator) && publicStatus && (
       <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
     );
 
@@ -196,13 +196,12 @@ class ActionBar extends React.PureComponent {
     //if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
     // else if (status.get('visibility') === 'private') reblogIcon = 'lock';
 
-    let reblog_disabled = (status.get('visibility') === 'direct' || (status.get('visibility') === 'private' && me !== status.getIn(['account', 'id'])));
     let reblog_message  = status.get('visibility') === 'private' ? messages.reblog_private : messages.reblog;
 
     return (
       <div className='detailed-status__action-bar'>
         <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_id', null) === null ? 'reply' : 'reply-all'} onClick={this.handleReplyClick} /></div>
-        <div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(reblog_message)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
+        <div className='detailed-status__button'><IconButton disabled={!publicStatus} active={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(reblog_message)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
         <div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
         {shareButton}
         <div className='detailed-status__button'><IconButton className='bookmark-icon' active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
diff --git a/app/javascript/flavours/glitch/styles/components/announcements.scss b/app/javascript/flavours/glitch/styles/components/announcements.scss
index ac4c199cd..eab6c728b 100644
--- a/app/javascript/flavours/glitch/styles/components/announcements.scss
+++ b/app/javascript/flavours/glitch/styles/components/announcements.scss
@@ -84,14 +84,13 @@
 
     &__unread {
       position: absolute;
-      top: 15px;
-      right: 15px;
-      display: inline-block;
+      top: 19px;
+      right: 19px;
+      display: block;
       background: $highlight-text-color;
       border-radius: 50%;
       width: 0.625rem;
       height: 0.625rem;
-      margin: 0 .15em;
     }
   }
 
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index 4b3c79d0d..e2c8d43c9 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -319,7 +319,7 @@ class StatusActionBar extends ImmutablePureComponent {
       replyTitle = intl.formatMessage(messages.replyAll);
     }
 
-    const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
+    const shareButton = ('share' in navigator) && publicStatus && (
       <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
     );
 
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index 657d2bb1c..959774da4 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -250,7 +250,7 @@ class ActionBar extends React.PureComponent {
       }
     }
 
-    const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
+    const shareButton = ('share' in navigator) && publicStatus && (
       <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
     );
 
@@ -265,12 +265,10 @@ class ActionBar extends React.PureComponent {
     if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
     else if (status.get('visibility') === 'private') reblogIcon = 'lock';
 
-    let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private');
-
     return (
       <div className='detailed-status__action-bar'>
         <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
-        <div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
+        <div className='detailed-status__button'><IconButton disabled={!publicStatus} active={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
         <div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
         {shareButton}
         <div className='detailed-status__button'><IconButton className='bookmark-icon' active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index 596ebc051..c71212b85 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -184,8 +184,8 @@
   "home.column_settings.basic": "الأساسية",
   "home.column_settings.show_reblogs": "اعرض الترقيات",
   "home.column_settings.show_replies": "اعرض الردود",
-  "home.hide_announcements": "Hide announcements",
-  "home.show_announcements": "Show announcements",
+  "home.hide_announcements": "إخفاء الإعلانات",
+  "home.show_announcements": "إظهار الإعلانات",
   "intervals.full.days": "{number, plural, one {# يوم} other {# أيام}}",
   "intervals.full.hours": "{number, plural, one {# ساعة} other {# ساعات}}",
   "intervals.full.minutes": "{number, plural, one {# دقيقة} other {# دقائق}}",
@@ -335,7 +335,7 @@
   "relative_time.just_now": "الآن",
   "relative_time.minutes": "{number}د",
   "relative_time.seconds": "{number}ثا",
-  "relative_time.today": "today",
+  "relative_time.today": "اليوم",
   "reply_indicator.cancel": "إلغاء",
   "report.forward": "التحويل إلى {target}",
   "report.forward_hint": "هذا الحساب ينتمي إلى خادوم آخَر. هل تودّ إرسال نسخة مجهولة مِن التقرير إلى هنالك أيضًا؟",
diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json
index 8a2657792..8006a1306 100644
--- a/app/javascript/mastodon/locales/ast.json
+++ b/app/javascript/mastodon/locales/ast.json
@@ -29,14 +29,14 @@
   "account.never_active": "Enxamás",
   "account.posts": "Barritos",
   "account.posts_with_replies": "Barritos y rempuestes",
-  "account.report": "Informar de @{name}",
+  "account.report": "Report @{name}",
   "account.requested": "Esperando pola aprobación. Calca pa encaboxar la solicitú de siguimientu",
   "account.share": "Share @{name}'s profile",
-  "account.show_reblogs": "Show boosts from @{name}",
+  "account.show_reblogs": "Amosar les comparticiones de @{name}",
   "account.unblock": "Desbloquiar a @{name}",
   "account.unblock_domain": "Amosar {domain}",
   "account.unendorse": "Nun destacar nel perfil",
-  "account.unfollow": "Unfollow",
+  "account.unfollow": "Dexar de siguir",
   "account.unmute": "Unmute @{name}",
   "account.unmute_notifications": "Unmute notifications from @{name}",
   "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
@@ -84,7 +84,7 @@
   "compose_form.poll.add_option": "Amestar una escoyeta",
   "compose_form.poll.duration": "Poll duration",
   "compose_form.poll.option_placeholder": "Escoyeta {number}",
-  "compose_form.poll.remove_option": "Remove this choice",
+  "compose_form.poll.remove_option": "Desaniciar esta escoyeta",
   "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
   "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
   "compose_form.publish": "Barritar",
@@ -96,30 +96,30 @@
   "compose_form.spoiler.unmarked": "El testu nun va anubrise",
   "compose_form.spoiler_placeholder": "Escribi equí l'alvertencia",
   "confirmation_modal.cancel": "Encaboxar",
-  "confirmations.block.block_and_report": "Block & Report",
-  "confirmations.block.confirm": "Block",
+  "confirmations.block.block_and_report": "Bloquiar ya informar",
+  "confirmations.block.confirm": "Bloquiar",
   "confirmations.block.message": "¿De xuru que quies bloquiar a {name}?",
-  "confirmations.delete.confirm": "Delete",
+  "confirmations.delete.confirm": "Desaniciar",
   "confirmations.delete.message": "¿De xuru que quies desaniciar esti estáu?",
   "confirmations.delete_list.confirm": "Desaniciar",
   "confirmations.delete_list.message": "¿De xuru que quies desaniciar dafechu esta llista?",
   "confirmations.domain_block.confirm": "Anubrir tol dominiu",
-  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
-  "confirmations.logout.confirm": "Log out",
-  "confirmations.logout.message": "Are you sure you want to log out?",
-  "confirmations.mute.confirm": "Mute",
-  "confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
+  "confirmations.domain_block.message": "¿De xuru xurísimu que quies bloquiar el dominiu {domain} enteru? Na mayoría de casos bloquiar o silenciar dalguna cuenta ye abondo y preferible. Nun vas ver el conteníu d'esi dominiu en nenguna llinia temporal pública o nos avisos, y van desanciase los tos siguidores d'esi dominiu.",
+  "confirmations.logout.confirm": "Zarrar sesión",
+  "confirmations.logout.message": "¿De xuru que quies zarrar la sesión?",
+  "confirmations.mute.confirm": "Silenciar",
+  "confirmations.mute.explanation": "Esto va anubrir los espublizamientos y les sos menciones pero entá va permiti-yos ver los tos espublizamientos y siguite.",
   "confirmations.mute.message": "¿De xuru que quies silenciar a {name}?",
   "confirmations.redraft.confirm": "Desaniciar y reeditar",
   "confirmations.redraft.message": "¿De xuru que quies desaniciar esti estáu y reeditalu? Van perdese los favoritos y comparticiones, y les rempuestes al toot orixinal van quedar güérfanes.",
-  "confirmations.reply.confirm": "Reply",
-  "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
-  "confirmations.unfollow.confirm": "Unfollow",
+  "confirmations.reply.confirm": "Responder",
+  "confirmations.reply.message": "Responder agora va sobrescribir el mensaxe que tas componiendo anguaño. ¿De xuru que quies siguir?",
+  "confirmations.unfollow.confirm": "Dexar de siguir",
   "confirmations.unfollow.message": "¿De xuru que quies dexar de siguir a {name}?",
   "conversation.delete": "Delete conversation",
   "conversation.mark_as_read": "Mark as read",
   "conversation.open": "View conversation",
-  "conversation.with": "With {names}",
+  "conversation.with": "Con {names}",
   "directory.federated": "Dende'l fediversu",
   "directory.local": "Dende {domain} namái",
   "directory.new_arrivals": "Cuentes nueves",
@@ -142,21 +142,21 @@
   "emoji_button.travel": "Viaxes y llugares",
   "empty_column.account_timeline": "¡Equí nun hai barritos!",
   "empty_column.account_unavailable": "Profile unavailable",
-  "empty_column.blocks": "Entá nun bloquiesti a dengún usuariu.",
+  "empty_column.blocks": "Entá nun bloquiesti a nunengún usuariu.",
   "empty_column.bookmarked_statuses": "Entá nun tienes nengún barritu en Marcadores. Cuando amiestes unu, va amosase equí.",
   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
-  "empty_column.direct": "Entá nun tienes dengún mensaxe direutu. Cuando unvies o recibas dalgún, va apaecer equí.",
+  "empty_column.direct": "Entá nun tienes nunengún mensaxe direutu. Cuando unvies o recibas dalgún, va apaecer equí.",
   "empty_column.domain_blocks": "Entá nun hai dominios anubríos.",
   "empty_column.favourited_statuses": "Entá nun tienes nengún barritu en Favoritos. Cuando amiestes unu, va amosase equí.",
   "empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
-  "empty_column.follow_requests": "Entá nun tienes denguna solicitú de siguimientu. Cuando recibas una, va amosase equí.",
+  "empty_column.follow_requests": "Entá nun tienes nenguna solicitú de siguimientu. Cuando recibas una, va amosase equí.",
   "empty_column.hashtag": "Entá nun hai nada nesta etiqueta.",
   "empty_column.home": "¡Tienes la llinia temporal balera! Visita {public} o usa la gueta pa entamar y conocer a otros usuarios.",
   "empty_column.home.public_timeline": "la llinia temporal pública",
   "empty_column.list": "Entá nun hai nada nesta llista. Cuando los miembros d'esta llista espublicen estaos nuevos, van apaecer equí.",
-  "empty_column.lists": "Entá nun tienes denguna llista. Cuando crees una, va amosase equí.",
-  "empty_column.mutes": "Entá nun silenciesti a dengún usuariu.",
-  "empty_column.notifications": "Entá nun tienes dengún avisu. Interactúa con otros p'aniciar la conversación.",
+  "empty_column.lists": "Entá nun tienes nunenguna llista. Cuando crees una, va amosase equí.",
+  "empty_column.mutes": "Entá nun silenciesti a nunengún usuariu.",
+  "empty_column.notifications": "Entá nun tienes nunengún avisu. Interactúa con otros p'aniciar la conversación.",
   "empty_column.public": "¡Equí nun hai nada! Escribi daqué público o sigui a usuarios d'otros sirvidores pa rellenar esto",
   "error.unexpected_crash.explanation": "Pola mor d'un fallu nel códigu o un problema de compatibilidá del restolador, esta páxina nun pudo amosase correutamente.",
   "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
@@ -189,7 +189,7 @@
   "intervals.full.days": "{number, plural, one {# día} other {# díes}}",
   "intervals.full.hours": "{number, plural, one {# hora} other {# hores}}",
   "intervals.full.minutes": "{number, plural, one {# minutu} other {# minutos}}",
-  "introduction.federation.action": "Next",
+  "introduction.federation.action": "Siguiente",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Los espublizamientos públicos de los demás sirvidores del fediversu van apaecer na llinia temporal federada.",
   "introduction.federation.home.headline": "Home",
@@ -198,12 +198,12 @@
   "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
   "introduction.interactions.action": "¡Finar el tutorial!",
   "introduction.interactions.favourite.headline": "Favourite",
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
+  "introduction.interactions.favourite.text": "Pues guardar un barritu pa dempués y facer que l'autor sepa que te prestó marcándolu como favoritu.",
   "introduction.interactions.reblog.headline": "Boost",
   "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
   "introduction.interactions.reply.headline": "Reply",
   "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
-  "introduction.welcome.action": "Let's go!",
+  "introduction.welcome.action": "¡Vamos!",
   "introduction.welcome.headline": "Primeros pasos",
   "introduction.welcome.text": "¡Afáyate nel fediversu! Nun momentu vas ser a tresmitir mensaxes y falar colos collacios d'una montonera de sirvidores. Pero esti sirvidor, {domain}, ye especial: agospia'l to perfil, asina qu'alcuérdate de cómo se llama.",
   "keyboard_shortcuts.back": "pa dir p'atrás",
@@ -316,8 +316,8 @@
   "poll.total_votes": "{count, plural, one {# votu} other {# votos}}",
   "poll.vote": "Vote",
   "poll.voted": "You voted for this answer",
-  "poll_button.add_poll": "Add a poll",
-  "poll_button.remove_poll": "Remove poll",
+  "poll_button.add_poll": "Amestar una encuesta",
+  "poll_button.remove_poll": "Desaniciar la encuesta",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Post to mentioned users only",
   "privacy.direct.short": "Direct",
@@ -341,7 +341,7 @@
   "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
   "report.hint": "L'informe va unviase a los llendadores del to sirvidor. Embaxo, pues desplicar por qué informes d'esta cuenta:",
   "report.placeholder": "Comentarios adicionales",
-  "report.submit": "Submit",
+  "report.submit": "Unviar",
   "report.target": "Report {target}",
   "search.placeholder": "Guetar",
   "search_popout.search_format": "Formatu de gueta avanzada",
@@ -362,7 +362,7 @@
   "status.cancel_reblog_private": "Dexar de compartir",
   "status.cannot_reblog": "Esti artículu nun pue compartise",
   "status.copy": "Copiar l'enllaz al estáu",
-  "status.delete": "Delete",
+  "status.delete": "Desaniciar",
   "status.detailed_status": "Detailed conversation view",
   "status.direct": "Unviar un mensaxe direutu a @{name}",
   "status.embed": "Empotrar",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 11092a133..f55420793 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -158,8 +158,8 @@
   "empty_column.mutes": "Encara no has silenciat cap usuari.",
   "empty_column.notifications": "Encara no tens notificacions. Interactua amb altres per iniciar la conversa.",
   "empty_column.public": "No hi ha res aquí! Escriu públicament alguna cosa o manualment segueix usuaris d'altres servidors per omplir-ho",
-  "error.unexpected_crash.explanation": "A causa d'un bug en el nostre codi o un problema de compatibilitat del navegador, aquesta pàgina no podria ser mostrada correctament.",
-  "error.unexpected_crash.next_steps": "Prova recarregant la pàgina. Si això no ajuda encara pots ser capaç d'utilitzar Mastodont a través d'un navegador diferent o app nativa.",
+  "error.unexpected_crash.explanation": "A causa d'un bug en el nostre codi o un problema de compatibilitat del navegador, aquesta pàgina podria no ser mostrada correctament.",
+  "error.unexpected_crash.next_steps": "Prova recarregant la pàgina. Si això no ajuda, encara podries ser capaç d'utilitzar Mastodon a través d'un navegador diferent o amb una app nativa.",
   "errors.unexpected_crash.copy_stacktrace": "Còpia stacktrace al porta-retalls",
   "errors.unexpected_crash.report_issue": "Informa d'un problema",
   "follow_request.authorize": "Autoritzar",
diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index 890baec0b..0d535cbc2 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -184,8 +184,8 @@
   "home.column_settings.basic": "Syml",
   "home.column_settings.show_reblogs": "Dangos bŵstiau",
   "home.column_settings.show_replies": "Dangos ymatebion",
-  "home.hide_announcements": "Hide announcements",
-  "home.show_announcements": "Show announcements",
+  "home.hide_announcements": "Cuddio cyhoeddiadau",
+  "home.show_announcements": "Dangos cyhoeddiadau",
   "intervals.full.days": "{number, plural, one {# ddydd} other {# o ddyddiau}}",
   "intervals.full.hours": "{number, plural, one {# awr} other {# o oriau}}",
   "intervals.full.minutes": "{number, plural, one {# funud} other {# o funudau}}",
@@ -335,7 +335,7 @@
   "relative_time.just_now": "nawr",
   "relative_time.minutes": "{number}munud",
   "relative_time.seconds": "{number}eiliad",
-  "relative_time.today": "today",
+  "relative_time.today": "heddiw",
   "reply_indicator.cancel": "Canslo",
   "report.forward": "Ymlaen i {target}",
   "report.forward_hint": "Mae'r cyfrif o weinydd arall. Anfon copi anhysbys o'r adroddiad yno hefyd?",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 798557200..76657cdde 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -72,7 +72,7 @@
   "column_header.moveRight_settings": "Mover columna cara a dereita",
   "column_header.pin": "Fixar",
   "column_header.show_settings": "Amosar axustes",
-  "column_header.unpin": "Desafixar",
+  "column_header.unpin": "Desapegar",
   "column_subheading.settings": "Axustes",
   "community.column_settings.media_only": "Só multimedia",
   "compose_form.direct_message_warning": "Este toot só será enviado ás usuarias mencionadas.",
@@ -118,7 +118,7 @@
   "confirmations.unfollow.message": "Desexas deixar de seguir a {name}?",
   "conversation.delete": "Eliminar conversa",
   "conversation.mark_as_read": "Marcar como lido",
-  "conversation.open": "Ollar conversa",
+  "conversation.open": "Ver conversa",
   "conversation.with": "Con {names}",
   "directory.federated": "Do fediverso coñecido",
   "directory.local": "Só de {domain}",
@@ -129,7 +129,7 @@
   "emoji_button.activity": "Actividade",
   "emoji_button.custom": "Personalizado",
   "emoji_button.flags": "Bandeiras",
-  "emoji_button.food": "Comida e bebida",
+  "emoji_button.food": "Comida e Bebida",
   "emoji_button.label": "Inserir emoticona",
   "emoji_button.nature": "Natureza",
   "emoji_button.not_found": "Non hai emoticonas!! (╯°□°)╯︵ ┻━┻",
@@ -139,11 +139,11 @@
   "emoji_button.search": "Procurar...",
   "emoji_button.search_results": "Resultados da procura",
   "emoji_button.symbols": "Símbolos",
-  "emoji_button.travel": "Viaxes e lugares",
+  "emoji_button.travel": "Viaxes e Lugares",
   "empty_column.account_timeline": "Non hai toots aquí!",
-  "empty_column.account_unavailable": "Perfil non dispoñíbel",
+  "empty_column.account_unavailable": "Perfil non dispoñible",
   "empty_column.blocks": "Aínda non bloqueaches a ningún usuaria.",
-  "empty_column.bookmarked_statuses": "Aínda non marcaches ningún toot. Cando o fagas, amosaranse aquí.",
+  "empty_column.bookmarked_statuses": "Aínda non marcaches ningún toot. Cando o fagas, aparecerán aquí.",
   "empty_column.community": "A cronoloxía local está baleira. Escribe algo de xeito público para espallalo!",
   "empty_column.direct": "Aínda non tes mensaxes directas. Cando envíes ou recibas unha, amosarase aquí.",
   "empty_column.domain_blocks": "Aínda non hai dominios agochados.",
@@ -165,7 +165,7 @@
   "follow_request.authorize": "Autorizar",
   "follow_request.reject": "Rexeitar",
   "getting_started.developers": "Desenvolvedores",
-  "getting_started.directory": "Directorio do perfil",
+  "getting_started.directory": "Directorio local",
   "getting_started.documentation": "Documentación",
   "getting_started.heading": "Primeiros pasos",
   "getting_started.invite": "Convidar persoas",
@@ -223,13 +223,13 @@
   "keyboard_shortcuts.hotkey": "Tecla de atallo",
   "keyboard_shortcuts.legend": "para amosar esta lenda",
   "keyboard_shortcuts.local": "para abrir a cronoloxía local",
-  "keyboard_shortcuts.mention": "para mencionar ó autor",
+  "keyboard_shortcuts.mention": "para mencionar a autora",
   "keyboard_shortcuts.muted": "abrir lista de usuarias acaladas",
   "keyboard_shortcuts.my_profile": "para abrir o teu perfil",
   "keyboard_shortcuts.notifications": "para abrir a columna das notificacións",
   "keyboard_shortcuts.open_media": "para abrir o contido multimedia",
   "keyboard_shortcuts.pinned": "para abrir a listaxe dos toots fixados",
-  "keyboard_shortcuts.profile": "para abrir o perfil do autor",
+  "keyboard_shortcuts.profile": "para abrir o perfil da autora",
   "keyboard_shortcuts.reply": "para responder",
   "keyboard_shortcuts.requests": "para abrir a listaxe das peticións de seguimento",
   "keyboard_shortcuts.search": "para destacar a procura",
@@ -313,7 +313,7 @@
   "poll.closed": "Pechado",
   "poll.refresh": "Actualizar",
   "poll.total_people": "{count, plural,one {# persoa}other {# persoas}}",
-  "poll.total_votes": "{count, plural, one {# voto} outros {# votos}}",
+  "poll.total_votes": "{count, plural, one {# voto} other {# votos}}",
   "poll.vote": "Votar",
   "poll.voted": "Votaches por esta opción",
   "poll_button.add_poll": "Engadir unha enquisa",
@@ -357,14 +357,14 @@
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "status.admin_account": "Abrir interface de moderación para @{name}",
   "status.admin_status": "Abrir este estado na interface de moderación",
-  "status.block": "Bloquear @{name}",
+  "status.block": "Bloquear a @{name}",
   "status.bookmark": "Marcar",
   "status.cancel_reblog_private": "Desfacer compartido",
   "status.cannot_reblog": "Esta publicación non pode ser promovida",
   "status.copy": "Copiar ligazón ó estado",
   "status.delete": "Eliminar",
   "status.detailed_status": "Vista detallada da conversa",
-  "status.direct": "Mensaxe directa @{name}",
+  "status.direct": "Mensaxe directa a @{name}",
   "status.embed": "Embeber nunha web",
   "status.favourite": "Favorito",
   "status.filtered": "Filtrado",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index 8cf3f3e58..98d0af510 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Batasan tingkat",
   "alert.unexpected.message": "Terjadi kesalahan yang tidak terduga.",
   "alert.unexpected.title": "Oops!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Pengumuman",
   "autosuggest_hashtag.per_week": "{count} per minggu",
   "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
   "bundle_column_error.body": "Kesalahan terjadi saat memuat komponen ini.",
diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json
index d73110508..e78b4cc4f 100644
--- a/app/javascript/mastodon/locales/kab.json
+++ b/app/javascript/mastodon/locales/kab.json
@@ -28,7 +28,7 @@
   "account.muted": "Yettwasgugem",
   "account.never_active": "Werǧin",
   "account.posts": "Tijewwaqin",
-  "account.posts_with_replies": "Tibarraḥin d tririyin",
+  "account.posts_with_replies": "Tijewwaqin akked tririyin",
   "account.report": "Sewɛed @{name}",
   "account.requested": "Di laɛḍil ad yettwaqbel. Ssit iwakken ad yefsex usuter n weḍfar",
   "account.share": "Bḍu amaγnu n @{name}",
@@ -41,7 +41,7 @@
   "account.unmute_notifications": "Serreḥ ilγa sγur @{name}",
   "alert.rate_limited.message": "Ma ulac aγilif ɛreḍ tikelt-nniḍen mbeɛd {retry_time, time, medium}.",
   "alert.rate_limited.title": "Aktum s talast",
-  "alert.unexpected.message": "Tella-d tuccḍa i ɣef ur nedmi ara.",
+  "alert.unexpected.message": "Tella-d tuccḍa i γef ur nedmi ara.",
   "alert.unexpected.title": "Ayhuh!",
   "announcement.announcement": "Ulγu",
   "autosuggest_hashtag.per_week": "{count} i yimalas",
@@ -109,18 +109,18 @@
   "confirmations.logout.message": "D tidet tebγiḍ ad teffγeḍ?",
   "confirmations.mute.confirm": "Sgugem",
   "confirmations.mute.explanation": "Aya ad yeffer iznan-is d wid i deg d-yettwabder neγ d-tettwabder, maca xas akka yezmer neγ tezmer awali n yiznan-inek d uḍfaṛ-ik.",
-  "confirmations.mute.message": "Tetḥeqqeḍ belli tebγiḍ asɛuggen n {name}?",
+  "confirmations.mute.message": "Tetḥeqqeḍ belli tebγiḍ ad ttegugmeḍ {name}?",
   "confirmations.redraft.confirm": "Sfeḍ & Ɛiwed tira",
   "confirmations.redraft.message": "Tetḥeqqeḍ belli tebγiḍ tuksa n waddad-agi iwakken ad s-tɛiwdeḍ tira? Ismenyifen d beḍḍuwat ad ṛuḥen, ma d tiririyin-is ad uγalent d tigujilin.",
   "confirmations.reply.confirm": "Err",
   "confirmations.reply.message": "Tiririt akka tura ad k-degger izen-agi i tettaruḍ. Tebγiḍ ad tkemmleḍ?",
   "confirmations.unfollow.confirm": "Ur ḍḍafaṛ ara",
-  "confirmations.unfollow.message": "Tetḥeqqeḍ belli tebγiḍ ur teḍḍafaṛeḍ ara {name}?",
+  "confirmations.unfollow.message": "Tetḥeqqeḍ belli tebγiḍ ur teṭafaṛeḍ ara {name}?",
   "conversation.delete": "Sfeḍ adiwenni",
   "conversation.mark_as_read": "Creḍ yettwaγṛa",
   "conversation.open": "Sken adiwenni",
   "conversation.with": "Akked {names}",
-  "directory.federated": "Seg fedivers yettwasnen",
+  "directory.federated": "Deg fedivers yettwasnen",
   "directory.local": "Seg {domain} kan",
   "directory.new_arrivals": "Imaynuten id yewḍen",
   "directory.recently_active": "Yermed xas melmi kan",
@@ -205,7 +205,7 @@
   "introduction.interactions.reply.text": "Tzemreḍ ad terreḍ γef tjewwakin-ik d tid n medden-nniḍen, d acu ara tent-id-iɛeqden ta deffir ta deg yiwen udiwenni.",
   "introduction.welcome.action": "Bdu!",
   "introduction.welcome.headline": "Isurifen imenza",
-  "introduction.welcome.text": "Anṣuf γer fediverse! Deg kra n yimiren, ad tizmireḍ ad tzzuzreḍ iznan neɣ ad tmeslayeḍ i yemddukkal deg waṭas n yiqeddacen. Maca aqeddac-agi, {domain}, mačči am wiyaḍ - deg-s i yella umaγnu-ik, ihi cfu γef yisem-is.",
+  "introduction.welcome.text": "Anṣuf γer fediverse! Deg kra n yimiren, ad tizmireḍ ad tzzuzreḍ iznan neγ ad tmeslayeḍ i yemddukkal deg waṭas n yiqeddacen. Maca aqeddac-agi, {domain}, mačči am wiyaḍ - deg-s i yella umaγnu-ik, ihi cfu γef yisem-is.",
   "keyboard_shortcuts.back": "uγal ar deffir",
   "keyboard_shortcuts.blocked": "akken ad teldiḍ tabdert n yimseqdacen yettwasḥebsen",
   "keyboard_shortcuts.boost": "i beṭṭu tikelt-nniḍen",
@@ -412,8 +412,8 @@
   "trends.count_by_accounts": "{count} {rawCount, plural, one {n umdan} other {n yemdanen}} i yettmeslayen",
   "trends.trending_now": "Trending now",
   "ui.beforeunload": "Arewway-ik·im ad iruḥ ma yella tefeγ-ed deg Maṣṭudun.",
-  "upload_area.title": "Drag & drop to upload",
-  "upload_button.label": "Add media ({formats})",
+  "upload_area.title": "Zuḥeb rnu sers i tasalyt",
+  "upload_button.label": "Rnu Taγwalt ({formats})",
   "upload_error.limit": "File upload limit exceeded.",
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.audio_description": "Glem-d i yemdanen i yesɛan ugur deg tmesliwt",
@@ -427,7 +427,7 @@
   "upload_modal.detect_text": "Sefru-d aḍris seg tugna",
   "upload_modal.edit_media": "Ẓreg taγwalt",
   "upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
-  "upload_modal.preview_label": "Preview ({ratio})",
+  "upload_modal.preview_label": "Taskant ({ratio})",
   "upload_progress.label": "Asali iteddu...",
   "video.close": "Mdel tabidyutt",
   "video.download": "Sidered afaylu",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index 9d119902a..9d9eaa769 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -1,7 +1,7 @@
 {
   "account.add_or_remove_from_list": "Dodaj lub usuń z list",
   "account.badges.bot": "Bot",
-  "account.badges.group": "Group",
+  "account.badges.group": "Grupa",
   "account.block": "Blokuj @{name}",
   "account.block_domain": "Blokuj wszystko z {domain}",
   "account.blocked": "Zablokowany(-a)",
@@ -43,7 +43,7 @@
   "alert.rate_limited.title": "Ograniczony czasowo",
   "alert.unexpected.message": "Wystąpił nieoczekiwany błąd.",
   "alert.unexpected.title": "O nie!",
-  "announcement.announcement": "Announcement",
+  "announcement.announcement": "Ogłoszenie",
   "autosuggest_hashtag.per_week": "{count} co tydzień",
   "boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
   "bundle_column_error.body": "Coś poszło nie tak podczas ładowania tego składnika.",
@@ -89,8 +89,8 @@
   "compose_form.poll.duration": "Czas trwania głosowania",
   "compose_form.poll.option_placeholder": "Opcja {number}",
   "compose_form.poll.remove_option": "Usuń tę opcję",
-  "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
-  "compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
+  "compose_form.poll.switch_to_multiple": "Pozwól na wybranie wielu opcji",
+  "compose_form.poll.switch_to_single": "Pozwól na wybranie tylko jednej opcji",
   "compose_form.publish": "Wyślij",
   "compose_form.publish_loud": "{publish}!",
   "compose_form.sensitive.hide": "Oznacz multimedia jako wrażliwe",
@@ -188,8 +188,8 @@
   "home.column_settings.basic": "Podstawowe",
   "home.column_settings.show_reblogs": "Pokazuj podbicia",
   "home.column_settings.show_replies": "Pokazuj odpowiedzi",
-  "home.hide_announcements": "Hide announcements",
-  "home.show_announcements": "Show announcements",
+  "home.hide_announcements": "Ukryj ogłoszenia",
+  "home.show_announcements": "Pokaż ogłoszenia",
   "intervals.full.days": "{number, plural, one {# dzień} few {# dni} many {# dni} other {# dni}}",
   "intervals.full.hours": "{number, plural, one {# godzina} few {# godziny} many {# godzin} other {# godzin}}",
   "intervals.full.minutes": "{number, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}",
@@ -340,7 +340,7 @@
   "relative_time.just_now": "teraz",
   "relative_time.minutes": "{number} min.",
   "relative_time.seconds": "{number} s.",
-  "relative_time.today": "today",
+  "relative_time.today": "dzisiaj",
   "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ą?",
@@ -421,11 +421,11 @@
   "upload_button.label": "Dodaj zawartość multimedialną (JPEG, PNG, GIF, WebM, MP4, MOV)",
   "upload_error.limit": "Przekroczono limit plików do wysłania.",
   "upload_error.poll": "Dołączanie plików nie dozwolone z głosowaniami.",
-  "upload_form.audio_description": "Describe for people with hearing loss",
+  "upload_form.audio_description": "Opisz dla osób niesłyszących i niedosłyszących",
   "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących",
   "upload_form.edit": "Edytuj",
   "upload_form.undo": "Usuń",
-  "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
+  "upload_form.video_description": "Opisz dla osób niesłyszących, niedosłyszących, niewidomych i niedowidzących",
   "upload_modal.analyzing_picture": "Analizowanie obrazu…",
   "upload_modal.apply": "Zastosuj",
   "upload_modal.description_placeholder": "Pchnąć w tę łódź jeża lub ośm skrzyń fig",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 8217a1369..ad4407e9d 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -184,8 +184,8 @@
   "home.column_settings.basic": "Základné",
   "home.column_settings.show_reblogs": "Ukáž vyzdvihnuté",
   "home.column_settings.show_replies": "Ukáž odpovede",
-  "home.hide_announcements": "Hide announcements",
-  "home.show_announcements": "Show announcements",
+  "home.hide_announcements": "Skry oboznámenia",
+  "home.show_announcements": "Ukáž oboznámenia",
   "intervals.full.days": "{number, plural, one {# deň} few {# dní} many {# dní} other {# dní}}",
   "intervals.full.hours": "{number, plural, one {# hodina} few {# hodín} many {# hodín} other {# hodín}}",
   "intervals.full.minutes": "{number, plural, one {# minúta} few {# minút} many {# minút} other {# minút}}",
@@ -335,7 +335,7 @@
   "relative_time.just_now": "teraz",
   "relative_time.minutes": "{number}min",
   "relative_time.seconds": "{number}sek",
-  "relative_time.today": "today",
+  "relative_time.today": "dnes",
   "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 hlásenia aj tam?",
diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json
index 1674499e9..003dfaf3b 100644
--- a/app/javascript/mastodon/locales/ta.json
+++ b/app/javascript/mastodon/locales/ta.json
@@ -47,7 +47,7 @@
   "autosuggest_hashtag.per_week": "ஒவ்வொரு வாரம் {count}",
   "boost_modal.combo": "நீங்கள் இதை அடுத்தமுறை தவிர்க்க {combo} வை அழுத்தவும்",
   "bundle_column_error.body": "இக்கூற்றை ஏற்றம் செய்யும்பொழுது ஏதோ தவறு ஏற்பட்டுள்ளது.",
-  "bundle_column_error.retry": "மீண்டும் முயல்க",
+  "bundle_column_error.retry": "மீண்டும் முயற்சிக்கவும்",
   "bundle_column_error.title": "பிணையப் பிழை",
   "bundle_modal_error.close": "மூடுக",
   "bundle_modal_error.message": "இக்கூற்றை ஏற்றம் செய்யும்பொழுது ஏதோ தவறு ஏற்பட்டுள்ளது.",
@@ -65,70 +65,70 @@
   "column.mutes": "மௌனத் தடை செய்யப்பட்ட பயனர்கள்",
   "column.notifications": "அறிவிப்புகள்",
   "column.pins": "பொருத்தப்பட்ட டூட்டுகள்",
-  "column.public": "கூட்டாட்சி காலக்கெடு",
+  "column.public": "ஆலமரத்தில் நிகழ்பவை",
   "column_back_button.label": "திரும்பு",
   "column_header.hide_settings": "அமைப்புகளை மறை",
-  "column_header.moveLeft_settings": "நெடுவரிசையை இடதுபுறமாக நகர்த்தவும்",
-  "column_header.moveRight_settings": "நெடுவரிசை வலது புறமாக நகர்த்து",
-  "column_header.pin": "குண்டூசி",
+  "column_header.moveLeft_settings": "நெடுவரிசையை இடதுபுறமாக நகர்த்து",
+  "column_header.moveRight_settings": "நெடுவரிசையை வலதுபுறமாக நகர்த்து",
+  "column_header.pin": "பொருத்து",
   "column_header.show_settings": "அமைப்புகளைக் காட்டு",
-  "column_header.unpin": "பொருத்தப்படாத",
+  "column_header.unpin": "கழட்டு",
   "column_subheading.settings": "அமைப்புகள்",
   "community.column_settings.media_only": "படங்கள் மட்டுமே",
-  "compose_form.direct_message_warning": "This toot will only be sent to all the mentioned users.",
-  "compose_form.direct_message_warning_learn_more": "மேலும் அறிக",
-  "compose_form.hashtag_warning": "இந்த toot பட்டியலிடப்படாதது போல எந்த ஹேஸ்டேக்கின் கீழ் பட்டியலிடப்படாது. ஹேஸ்டேக் மூலம் பொது டோட்டல்கள் மட்டுமே தேட முடியும்.",
-  "compose_form.lock_disclaimer": "உங்கள் கணக்கு அல்ல {locked}. உங்களுடைய பின்தொடர்பவர் மட்டும் இடுகைகளை யாராவது காணலாம்.",
-  "compose_form.lock_disclaimer.lock": "தாழிடு",
-  "compose_form.placeholder": "What is on your mind?",
-  "compose_form.poll.add_option": "ஒரு விருப்பத்தைச் சேர்க்கவும்",
-  "compose_form.poll.duration": "வாக்கெடுப்பு காலம்",
-  "compose_form.poll.option_placeholder": "தேர்ந்தெடுப்ப {number}",
-  "compose_form.poll.remove_option": "இந்த விருப்பத்தை அகற்றவும்",
+  "compose_form.direct_message_warning": "இந்த டூட் இதில் குறிப்பிடப்பட்டுள்ள பயனர்களுக்கு மட்டுமே அனுப்பப்படும்.",
+  "compose_form.direct_message_warning_learn_more": "மேலும் அறிய",
+  "compose_form.hashtag_warning": "இது ஒரு பட்டியலிடப்படாத டூட் என்பதால் எந்த ஹேஷ்டேகின் கீழும் வராது. ஹேஷ்டேகின் மூலம் பொதுவில் உள்ள டூட்டுகளை மட்டுமே தேட முடியும்.",
+  "compose_form.lock_disclaimer": "உங்கள் கணக்கு {locked} செய்யப்படவில்லை. உங்கள் பதிவுகளை யார் வேண்டுமானாலும் பின்தொடர்ந்து காணலாம்.",
+  "compose_form.lock_disclaimer.lock": "பூட்டப்பட்டது",
+  "compose_form.placeholder": "உங்கள் மனதில் என்ன இருக்கிறது?",
+  "compose_form.poll.add_option": "தேர்வை சேர்",
+  "compose_form.poll.duration": "கருத்துக்கணிப்பின் கால அளவு",
+  "compose_form.poll.option_placeholder": "தேர்வு எண் {number}",
+  "compose_form.poll.remove_option": "இந்தத் தேர்வை அகற்று",
   "compose_form.poll.switch_to_multiple": "பல தேர்வுகளை அனுமதிக்குமாறு மாற்று",
   "compose_form.poll.switch_to_single": "ஒரே ஒரு தேர்வை மட்டும் அனுமதிக்குமாறு மாற்று",
-  "compose_form.publish": "டூட் செய்க",
+  "compose_form.publish": "டூட்",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive.hide": "அனைவருக்கும் ஏற்ற ஊடகம் இல்லை எனக் குறியிடுக",
-  "compose_form.sensitive.marked": "ஊடகம் உணர்திறன் என குறிக்கப்பட்டுள்ளது",
-  "compose_form.sensitive.unmarked": "ஊடகம் உணர்திறன் என குறிக்கப்படவில்லை",
-  "compose_form.spoiler.marked": "எச்சரிக்கை பின்னால் உரை மறைக்கப்பட்டுள்ளது",
-  "compose_form.spoiler.unmarked": "உரை மறைக்கப்படவில்லை",
-  "compose_form.spoiler_placeholder": "இங்கே உங்கள் எச்சரிக்கையை எழுதுங்கள்",
-  "confirmation_modal.cancel": "எதிராணை",
+  "compose_form.sensitive.hide": "அனைவருக்கும் ஏற்றப் படம் இல்லை எனக் குறியிடு",
+  "compose_form.sensitive.marked": "இப்படம் அனைவருக்கும் ஏற்றதல்ல எனக் குறியிடப்பட்டுள்ளது",
+  "compose_form.sensitive.unmarked": "இப்படம் அனைவருக்கும் ஏற்றதல்ல எனக் குறியிடப்படவில்லை",
+  "compose_form.spoiler.marked": "எச்சரிக்கையின் பின்னால் பதிவு மறைக்கப்பட்டுள்ளது",
+  "compose_form.spoiler.unmarked": "பதிவு மறைக்கப்படவில்லை",
+  "compose_form.spoiler_placeholder": "உங்கள் எச்சரிக்கையை இங்கு எழுதவும்",
+  "confirmation_modal.cancel": "ரத்து",
   "confirmations.block.block_and_report": "தடுத்துப் புகாரளி",
   "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.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.logout.confirm": "வெளியேறு",
-  "confirmations.logout.message": "கண்டிப்பாக வெளியேறத்தான் வேண்டுமா?",
-  "confirmations.mute.confirm": "ஊமையான",
-  "confirmations.mute.explanation": "இது அவர்களின் பதிவுகளையும், அவர்களைக் குறிப்பிடும் பதிவுகளையும் மறைத்துவிடும். ஆனால் அவர்கள் உங்கள் பதிவுகளைப் பார்க்கவும், உங்களைப் பின் தொடரவும் அனுமதிக்கப்படுவார்கள்.",
-  "confirmations.mute.message": "நிச்சயமாக நீங்கள் முடக்க விரும்புகிறீர்களா {name}?",
-  "confirmations.redraft.confirm": "நீக்கு & redraft",
-  "confirmations.redraft.message": "நிச்சயமாக இந்த நிலையை நீக்கி, அதை மறுபடியும் உருவாக்க வேண்டுமா? பிடித்தவை மற்றும் ஊக்கங்கள் இழக்கப்படும், மற்றும் அசல் இடுகையில் பதில்கள் அனாதையான இருக்கும்.",
-  "confirmations.reply.confirm": "பதில்",
-  "confirmations.reply.message": "இப்போது பதில், தற்போது நீங்கள் உருவாக்கும் செய்தி மேலெழுதப்படும். நீங்கள் தொடர விரும்புகிறீர்களா?",
-  "confirmations.unfollow.confirm": "பின்தொடராட்",
-  "confirmations.unfollow.message": "நிச்சயமாக நீங்கள் பின்தொடர விரும்புகிறீர்களா {name}?",
-  "conversation.delete": "உரையாடலை அழிக்கவும்",
+  "confirmations.logout.message": "நிச்சயமாக நீங்கள் வெளியேற விரும்புகிறீர்களா?",
+  "confirmations.mute.confirm": "அமைதியாக்கு",
+  "confirmations.mute.explanation": "இந்தத் தேர்வு அவர்களின் பதிவுகளையும், அவர்களைக் குறிப்பிடும் பதிவுகளையும் மறைத்துவிடும். ஆனால், அவர்களால் உங்களைப் பின்தொடர்ந்து உங்கள் பதிவுகளைக் காண முடியும்.",
+  "confirmations.mute.message": "{name}-ஐ நிச்சயமாக நீங்கள் அமைதியாக்க விரும்புகிறீர்களா?",
+  "confirmations.redraft.confirm": "பதிவை நீக்கி மறுவரைவு செய்",
+  "confirmations.redraft.message": "நிச்சயமாக நீங்கள் இந்தப் பதிவை நீக்கி மறுவரைவு செய்ய விரும்புகிறீர்களா? விருப்பங்களும் பகிர்வுகளும் அழிந்துபோகும், மேலும் மூலப் பதிவிற்கு வந்த மறுமொழிகள் தனித்துவிடப்படும்.",
+  "confirmations.reply.confirm": "மறுமொழி",
+  "confirmations.reply.message": "ஏற்கனவே ஒரு பதிவு எழுதப்பட்டுக்கொண்டிருக்கிறது. இப்பொழுது பதில் எழுத முனைந்தால் அது அழிக்கப்படும். பரவாயில்லையா?",
+  "confirmations.unfollow.confirm": "விலகு",
+  "confirmations.unfollow.message": "{name}-ஐப் பின்தொடர்வதை நிச்சயமாக நீங்கள் நிறுத்த விரும்புகிறீர்களா?",
+  "conversation.delete": "உரையாடலை அழி",
   "conversation.mark_as_read": "படிக்கபட்டதாகக் குறி",
-  "conversation.open": "உரையாடலைக் காண்க",
+  "conversation.open": "உரையாடலைக் காட்டு",
   "conversation.with": "{names} உடன்",
-  "directory.federated": "தெரிந்த ஃபெடிவெர்சிலிருந்து",
-  "directory.local": "{domain} இல் இருந்து மட்டும்",
+  "directory.federated": "ஆலமரத்தின் அறியப்பட்டப் பகுதியிலிருந்து",
+  "directory.local": "{domain} களத்திலிருந்து மட்டும்",
   "directory.new_arrivals": "புதிய வரவு",
-  "directory.recently_active": "தற்பொழுது இயாக்கதிலிருப்பவர்கள்",
-  "embed.instructions": "கீழே உள்ள குறியீட்டை நகலெடுப்பதன் மூலம் உங்கள் இணையதளத்தில் இந்த நிலையை உட்பொதிக்கவும்.",
-  "embed.preview": "இது போன்ற தோற்றத்தை இங்கு காணலாம்:",
-  "emoji_button.activity": "நடவடிக்கை",
-  "emoji_button.custom": "வழக்கம்",
-  "emoji_button.flags": "கொடி",
+  "directory.recently_active": "சற்றுமுன் செயல்பாட்டில் இருந்தவர்கள்",
+  "embed.instructions": "இந்தப் பதிவை உங்கள் வலைதளத்தில் பொதிக்கக் கீழே உள்ள வரிகளை காப்பி செய்யவும்.",
+  "embed.preview": "பார்க்க இப்படி இருக்கும்:",
+  "emoji_button.activity": "செயல்பாடு",
+  "emoji_button.custom": "தனிப்பயன்",
+  "emoji_button.flags": "கொடிகள்",
   "emoji_button.food": "உணவு மற்றும் பானம்",
   "emoji_button.label": "குறுப்படங்களை உள்ளிடு",
   "emoji_button.nature": "இயற்கை",
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 54372022a..aa885e241 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -6697,14 +6697,13 @@ noscript {
 
     &__unread {
       position: absolute;
-      top: 15px;
-      right: 15px;
-      display: inline-block;
+      top: 19px;
+      right: 19px;
+      display: block;
       background: $highlight-text-color;
       border-radius: 50%;
       width: 0.625rem;
       height: 0.625rem;
-      margin: 0 .15em;
     }
   }
 
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index f1a751f84..fcc99d009 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -77,6 +77,8 @@ class Formatter
 
   def reformat(html)
     sanitize(html, Sanitize::Config::MASTODON_STRICT)
+  rescue ArgumentError
+    ''
   end
 
   def plaintext(status)
diff --git a/app/lib/sanitize_config.rb b/app/lib/sanitize_config.rb
index 2b5d554b5..e3fc94ba6 100644
--- a/app/lib/sanitize_config.rb
+++ b/app/lib/sanitize_config.rb
@@ -2,7 +2,23 @@
 
 class Sanitize
   module Config
-    HTTP_PROTOCOLS ||= ['http', 'https', 'dat', 'dweb', 'ipfs', 'ipns', 'ssb', 'gopher', 'xmpp', 'magnet', :relative].freeze
+    HTTP_PROTOCOLS = %w(
+      http
+      https
+    ).freeze
+
+    LINK_PROTOCOLS = %w(
+      http
+      https
+      dat
+      dweb
+      ipfs
+      ipns
+      ssb
+      gopher
+      xmpp
+      magnet
+    ).freeze
 
     CLASS_WHITELIST_TRANSFORMER = lambda do |env|
       node = env[:node]
@@ -38,6 +54,22 @@ class Sanitize
       end
     end
 
+    UNSUPPORTED_HREF_TRANSFORMER = lambda do |env|
+      return unless env[:node_name] == 'a'
+
+      current_node = env[:node]
+
+      scheme = begin
+        if current_node['href'] =~ Sanitize::REGEX_PROTOCOL
+          Regexp.last_match(1).downcase
+        else
+          :relative
+        end
+      end
+
+      current_node.replace(current_node.text) unless LINK_PROTOCOLS.include?(scheme)
+    end
+
     MASTODON_STRICT ||= freeze_config(
       elements: %w(p br span a abbr del pre blockquote code b strong u sub sup i em h1 h2 h3 h4 h5 ul ol li),
 
@@ -56,13 +88,14 @@ class Sanitize
       },
 
       protocols: {
-        'a'          => { 'href' => HTTP_PROTOCOLS },
-        'blockquote' => { 'cite' => HTTP_PROTOCOLS },
+        'a'          => { 'href' => LINK_PROTOCOLS },
+        'blockquote' => { 'cite' => LINK_PROTOCOLS },
       },
 
       transformers: [
         CLASS_WHITELIST_TRANSFORMER,
         IMG_TAG_TRANSFORMER,
+        UNSUPPORTED_HREF_TRANSFORMER,
       ]
     )