about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2020-11-12 22:13:57 +0100
committerThibaut Girka <thib@sitedethib.com>2020-11-12 22:13:57 +0100
commitc077cdaba70eac154909cad412ece409acc2e688 (patch)
tree7d13d319ce62475de15014406635f32a8742ca4b /app
parent67125534bc0fd48a45d6cb17a5c78712d8e87150 (diff)
parent9870b175b477bbc984fc7945f1ebe07e3f2b0053 (diff)
Merge branch 'master' into glitch-soc/merge-upstream
Conflicts:
- `app/controllers/relationships_controller.rb`:
  Upstream changed a line too close to a glitch-soc only line related to
  glitch-soc's theming system.
  Applied upstream changes accordingly.
Diffstat (limited to 'app')
-rw-r--r--app/controllers/relationships_controller.rb9
-rw-r--r--app/helpers/application_helper.rb10
-rw-r--r--app/helpers/settings_helper.rb4
-rw-r--r--app/javascript/mastodon/actions/compose.js4
-rw-r--r--app/javascript/mastodon/actions/notifications.js9
-rw-r--r--app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js23
-rw-r--r--app/javascript/mastodon/features/picture_in_picture/components/header.js11
-rw-r--r--app/javascript/mastodon/locales/co.json2
-rw-r--r--app/javascript/mastodon/locales/es.json210
-rw-r--r--app/javascript/mastodon/locales/ru.json14
-rw-r--r--app/javascript/mastodon/locales/sa.json2
-rw-r--r--app/javascript/mastodon/locales/szl.json2
-rw-r--r--app/javascript/mastodon/locales/tai.json2
-rw-r--r--app/javascript/mastodon/locales/tr.json92
-rw-r--r--app/javascript/mastodon/locales/ug.json2
-rw-r--r--app/javascript/mastodon/locales/vi.json2
-rw-r--r--app/javascript/mastodon/locales/zgh.json68
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json4
-rw-r--r--app/javascript/mastodon/reducers/notifications.js3
-rw-r--r--app/javascript/styles/mastodon/components.scss7
-rw-r--r--app/javascript/styles/mastodon/variables.scss2
-rw-r--r--app/javascript/styles/mastodon/widgets.scss20
-rw-r--r--app/models/account_stat.rb25
-rw-r--r--app/models/form/account_batch.rb8
-rw-r--r--app/models/tag.rb2
-rw-r--r--app/views/relationships/_account.html.haml2
-rw-r--r--app/views/relationships/show.html.haml2
27 files changed, 319 insertions, 222 deletions
diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb
index f1ab980c8..d40770726 100644
--- a/app/controllers/relationships_controller.rb
+++ b/app/controllers/relationships_controller.rb
@@ -6,6 +6,7 @@ class RelationshipsController < ApplicationController
   before_action :authenticate_user!
   before_action :set_accounts, only: :show
   before_action :set_pack
+  before_action :set_relationships, only: :show
   before_action :set_body_classes
 
   helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
@@ -29,6 +30,10 @@ class RelationshipsController < ApplicationController
     @accounts = RelationshipFilter.new(current_account, filter_params).results.page(params[:page]).per(40)
   end
 
+  def set_relationships
+    @relationships = AccountRelationshipsPresenter.new(@accounts.pluck(:id), current_user.account_id)
+  end
+
   def form_account_batch_params
     params.require(:form_account_batch).permit(:action, account_ids: [])
   end
@@ -50,7 +55,9 @@ class RelationshipsController < ApplicationController
   end
 
   def action_from_button
-    if params[:unfollow]
+    if params[:follow]
+      'follow'
+    elsif params[:unfollow]
       'unfollow'
     elsif params[:remove_from_followers]
       'remove_from_followers'
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 321283178..2936545a0 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -89,6 +89,16 @@ module ApplicationHelper
     end
   end
 
+  def interrelationships_icon(relationships, account_id)
+    if relationships.following[account_id] && relationships.followed_by[account_id]
+      fa_icon('exchange', title: I18n.t('relationships.mutual'), class: 'fa-fw active passive')
+    elsif relationships.following[account_id]
+      fa_icon(locale_direction == 'ltr' ? 'arrow-right' : 'arrow-left', title: I18n.t('relationships.following'), class: 'fa-fw active')
+    elsif relationships.followed_by[account_id]
+      fa_icon(locale_direction == 'ltr' ? 'arrow-left' : 'arrow-right', title: I18n.t('relationships.followers'), class: 'fa-fw passive')
+    end
+  end
+
   def custom_emoji_tag(custom_emoji, animate = true)
     if animate
       image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:")
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 87718dc05..5b39497b6 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -40,6 +40,7 @@ module SettingsHelper
     kk: 'Қазақша',
     kn: 'ಕನ್ನಡ',
     ko: '한국어',
+    ku: 'سۆرانی',
     lt: 'Lietuvių',
     lv: 'Latviešu',
     mk: 'Македонски',
@@ -56,6 +57,8 @@ module SettingsHelper
     pt: 'Português',
     ro: 'Română',
     ru: 'Русский',
+    sa: 'संस्कृतम्',
+    sc: 'Sardu',
     sk: 'Slovenčina',
     sl: 'Slovenščina',
     sq: 'Shqip',
@@ -69,6 +72,7 @@ module SettingsHelper
     uk: 'Українська',
     ur: 'اُردُو',
     vi: 'Tiếng Việt',
+    zgh: 'ⵜⴰⵎⴰⵣⵉⵖⵜ',
     'zh-CN': '简体中文',
     'zh-HK': '繁體中文(香港)',
     'zh-TW': '繁體中文(臺灣)',
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 6ef12f7b9..a60373fd5 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -152,9 +152,7 @@ export function submitCompose(routerHistory) {
         'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
       },
     }).then(function (response) {
-      if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
-        routerHistory.push('/timelines/direct');
-      } else if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
+      if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
         routerHistory.goBack();
       }
 
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index c4fa66428..d40b65745 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -37,8 +37,9 @@ export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
 
 export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
 
-export const NOTIFICATIONS_SET_BROWSER_SUPPORT    = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
-export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';
+export const NOTIFICATIONS_SET_BROWSER_SUPPORT        = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
+export const NOTIFICATIONS_SET_BROWSER_PERMISSION     = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';
+export const NOTIFICATIONS_DISMISS_BROWSER_PERMISSION = 'NOTIFICATIONS_DISMISS_BROWSER_PERMISSION';
 
 defineMessages({
   mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
@@ -283,3 +284,7 @@ export function setBrowserPermission (value) {
     value,
   };
 }
+
+export const dismissBrowserPermission = () => ({
+  type: NOTIFICATIONS_DISMISS_BROWSER_PERMISSION,
+});
diff --git a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js
index 766c9bb5b..6daf75814 100644
--- a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js
+++ b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js
@@ -1,25 +1,42 @@
 import React from 'react';
 import Icon from 'mastodon/components/icon';
 import Button from 'mastodon/components/button';
-import { requestBrowserPermission } from 'mastodon/actions/notifications';
+import IconButton from 'mastodon/components/icon_button';
+import { requestBrowserPermission, dismissBrowserPermission } from 'mastodon/actions/notifications';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
-import { FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 
-export default @connect(() => {})
+const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
+
+export default @connect()
+@injectIntl
 class NotificationsPermissionBanner extends React.PureComponent {
 
   static propTypes = {
     dispatch: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
   };
 
   handleClick = () => {
     this.props.dispatch(requestBrowserPermission());
   }
 
+  handleClose = () => {
+    this.props.dispatch(dismissBrowserPermission());
+  }
+
   render () {
+    const { intl } = this.props;
+
     return (
       <div className='notifications-permission-banner'>
+        <div className='notifications-permission-banner__close'>
+          <IconButton icon='times' onClick={this.handleClose} title={intl.formatMessage(messages.close)} />
+        </div>
+
         <h2><FormattedMessage id='notifications_permission_banner.title' defaultMessage='Never miss a thing' /></h2>
         <p><FormattedMessage id='notifications_permission_banner.how_to_control' defaultMessage="To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled." values={{ icon: <Icon id='sliders' /> }} /></p>
         <Button onClick={this.handleClick}><FormattedMessage id='notifications_permission_banner.enable' defaultMessage='Enable desktop notifications' /></Button>
diff --git a/app/javascript/mastodon/features/picture_in_picture/components/header.js b/app/javascript/mastodon/features/picture_in_picture/components/header.js
index 4cb6de1a4..7dd199b75 100644
--- a/app/javascript/mastodon/features/picture_in_picture/components/header.js
+++ b/app/javascript/mastodon/features/picture_in_picture/components/header.js
@@ -7,12 +7,18 @@ import IconButton from 'mastodon/components/icon_button';
 import { Link } from 'react-router-dom';
 import Avatar from 'mastodon/components/avatar';
 import DisplayName from 'mastodon/components/display_name';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
 
 const mapStateToProps = (state, { accountId }) => ({
   account: state.getIn(['accounts', accountId]),
 });
 
 export default @connect(mapStateToProps)
+@injectIntl
 class Header extends ImmutablePureComponent {
 
   static propTypes = {
@@ -20,10 +26,11 @@ class Header extends ImmutablePureComponent {
     statusId: PropTypes.string.isRequired,
     account: ImmutablePropTypes.map.isRequired,
     onClose: PropTypes.func.isRequired,
+    intl: PropTypes.object.isRequired,
   };
 
   render () {
-    const { account, statusId, onClose } = this.props;
+    const { account, statusId, onClose, intl } = this.props;
 
     return (
       <div className='picture-in-picture__header'>
@@ -32,7 +39,7 @@ class Header extends ImmutablePureComponent {
           <DisplayName account={account} />
         </Link>
 
-        <IconButton icon='times' onClick={onClose} title='Close' />
+        <IconButton icon='times' onClick={onClose} title={intl.formatMessage(messages.close)} />
       </div>
     );
   }
diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json
index 26e5e9f20..10d5f9008 100644
--- a/app/javascript/mastodon/locales/co.json
+++ b/app/javascript/mastodon/locales/co.json
@@ -134,7 +134,7 @@
   "directory.new_arrivals": "Ultimi arrivi",
   "directory.recently_active": "Attività ricente",
   "embed.instructions": "Integrà stu statutu à u vostru situ cù u codice quì sottu.",
-  "embed.preview": "Assumiglierà à qualcosa cusì:",
+  "embed.preview": "Hà da parè à quessa:",
   "emoji_button.activity": "Attività",
   "emoji_button.custom": "Persunalizati",
   "emoji_button.flags": "Bandere",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index b4bfffebe..144b14c58 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -1,131 +1,131 @@
 {
   "account.account_note_header": "Nota",
-  "account.add_or_remove_from_list": "Agregar o quitar de listas",
+  "account.add_or_remove_from_list": "Agregar o eliminar de listas",
   "account.badges.bot": "Bot",
   "account.badges.group": "Grupo",
   "account.block": "Bloquear a @{name}",
-  "account.block_domain": "Bloquear dominio {domain}",
-  "account.blocked": "Cuenta bloqueada",
+  "account.block_domain": "Ocultar todo de {domain}",
+  "account.blocked": "Bloqueado",
   "account.browse_more_on_origin_server": "Ver más en el perfil original",
-  "account.cancel_follow_request": "Cancelar solicitud de seguimiento",
+  "account.cancel_follow_request": "Cancelar la solicitud de seguimiento",
   "account.direct": "Mensaje directo a @{name}",
-  "account.disable_notifications": "Dejar de notificarme cuando @{name} publica",
-  "account.domain_blocked": "Dominio bloqueado",
+  "account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo",
+  "account.domain_blocked": "Dominio oculto",
   "account.edit_profile": "Editar perfil",
-  "account.enable_notifications": "Notificarme cuando @{name} publica",
-  "account.endorse": "Recomendar en mi perfil",
+  "account.enable_notifications": "Notificarme cuando @{name} publique algo",
+  "account.endorse": "Mostrar en perfil",
   "account.follow": "Seguir",
-  "account.followers": "Seguidorxs",
-  "account.followers.empty": "Nadie sigue a @{name} aún.",
-  "account.followers_counter": "{count, plural, one {{counter} Seguidor o Seguidora} other {{counter} Seguidorxs}}",
+  "account.followers": "Seguidores",
+  "account.followers.empty": "Todavía nadie sigue a este usuario.",
+  "account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidores}}",
   "account.following_counter": "{count, plural, other {{counter} Siguiendo}}",
-  "account.follows.empty": "@{name} no sigue a nadie aún.",
+  "account.follows.empty": "Este usuario todavía no sigue a nadie.",
   "account.follows_you": "Te sigue",
-  "account.hide_reblogs": "Ocultar publicaciones compartidas por @{name}",
+  "account.hide_reblogs": "Ocultar retoots de @{name}",
   "account.last_status": "Última actividad",
-  "account.link_verified_on": "La propiedad de este enlace fue verificada el {date}",
-  "account.locked_info": "El estado de privacidad de esta cuenta está establecido como bloqueado. @{name} revisa manualmente revisa quién puede seguirle.",
-  "account.media": "Medios",
+  "account.link_verified_on": "El proprietario de este link fue comprobado el {date}",
+  "account.locked_info": "El estado de privacidad de esta cuenta està configurado como bloqueado. El proprietario debe revisar manualmente quien puede seguirle.",
+  "account.media": "Multimedia",
   "account.mention": "Mencionar a @{name}",
-  "account.moved_to": "{name} se ha cambiado a:",
+  "account.moved_to": "{name} se ha mudado a:",
   "account.mute": "Silenciar a @{name}",
   "account.mute_notifications": "Silenciar notificaciones de @{name}",
-  "account.muted": "Cuenta silenciada",
+  "account.muted": "Silenciado",
   "account.never_active": "Nunca",
-  "account.posts": "Publicaciones",
-  "account.posts_with_replies": "Publicaciones y respuestas",
+  "account.posts": "Toots",
+  "account.posts_with_replies": "Toots con respuestas",
   "account.report": "Reportar a @{name}",
-  "account.requested": "Esperando aprobación. Da clic para cancelar la solicitud de seguimiento",
+  "account.requested": "Esperando aprobación",
   "account.share": "Compartir el perfil de @{name}",
-  "account.show_reblogs": "Mostrar publicaciones compartidas por @{name}",
-  "account.statuses_counter": "{count, plural, one {{counter} Publicación} other {{counter} Publicaciones}}",
+  "account.show_reblogs": "Mostrar retoots de @{name}",
+  "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}",
   "account.unblock": "Desbloquear a @{name}",
-  "account.unblock_domain": "Desbloquear dominio {domain}",
-  "account.unendorse": "No recomendar en mi perfil",
+  "account.unblock_domain": "Mostrar a {domain}",
+  "account.unendorse": "No mostrar en el perfil",
   "account.unfollow": "Dejar de seguir",
   "account.unmute": "Dejar de silenciar a @{name}",
-  "account.unmute_notifications": "Dejar de silenciar notificaciones de @{name}",
-  "account_note.placeholder": "Haz clic par agregar una nota",
-  "alert.rate_limited.message": "Por favor, reintenta después de las {retry_time, time, medium}.",
-  "alert.rate_limited.title": "Acción limitada",
-  "alert.unexpected.message": "Ocurrió un error inesperado.",
-  "alert.unexpected.title": "¡Ay güey!",
+  "account.unmute_notifications": "Dejar de silenciar las notificaciones de @{name}",
+  "account_note.placeholder": "Clic para añadir nota",
+  "alert.rate_limited.message": "Por favor reintente después de {retry_time, time, medium}.",
+  "alert.rate_limited.title": "Tarifa limitada",
+  "alert.unexpected.message": "Hubo un error inesperado.",
+  "alert.unexpected.title": "¡Ups!",
   "announcement.announcement": "Anuncio",
   "autosuggest_hashtag.per_week": "{count} por semana",
-  "boost_modal.combo": "Puedes hacer clic en {combo} para saltar esto la próxima vez",
+  "boost_modal.combo": "Puedes hacer clic en {combo} para saltar este aviso la próxima vez",
   "bundle_column_error.body": "Algo salió mal al cargar este componente.",
-  "bundle_column_error.retry": "Intentar de nuevo",
+  "bundle_column_error.retry": "Inténtalo de nuevo",
   "bundle_column_error.title": "Error de red",
   "bundle_modal_error.close": "Cerrar",
   "bundle_modal_error.message": "Algo salió mal al cargar este componente.",
   "bundle_modal_error.retry": "Inténtalo de nuevo",
-  "column.blocks": "Personas bloqueadas",
+  "column.blocks": "Usuarios bloqueados",
   "column.bookmarks": "Marcadores",
   "column.community": "Línea de tiempo local",
   "column.direct": "Mensajes directos",
-  "column.directory": "Ver perfiles",
-  "column.domain_blocks": "Dominios bloqueados",
+  "column.directory": "Buscar perfiles",
+  "column.domain_blocks": "Dominios ocultados",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Solicitudes de seguimiento",
-  "column.home": "Principal",
+  "column.home": "Inicio",
   "column.lists": "Listas",
-  "column.mutes": "Cuentas silenciadas",
+  "column.mutes": "Usuarios silenciados",
   "column.notifications": "Notificaciones",
-  "column.pins": "Publicaciones fijadas",
-  "column.public": "Línea temporal federada",
-  "column_back_button.label": "Volver",
+  "column.pins": "Toots fijados",
+  "column.public": "Línea de tiempo federada",
+  "column_back_button.label": "Atrás",
   "column_header.hide_settings": "Ocultar configuración",
   "column_header.moveLeft_settings": "Mover columna a la izquierda",
   "column_header.moveRight_settings": "Mover columna a la derecha",
   "column_header.pin": "Fijar",
-  "column_header.show_settings": "Mostrar configuración",
-  "column_header.unpin": "Desfijar",
-  "column_subheading.settings": "Configuración",
-  "community.column_settings.local_only": "Sólo local",
-  "community.column_settings.media_only": "Sólo medios",
-  "community.column_settings.remote_only": "Sólo remoto",
-  "compose_form.direct_message_warning": "Esta publicación sólo será enviada las personas mencionadas.",
+  "column_header.show_settings": "Mostrar ajustes",
+  "column_header.unpin": "Dejar de fijar",
+  "column_subheading.settings": "Ajustes",
+  "community.column_settings.local_only": "Solo local",
+  "community.column_settings.media_only": "Solo media",
+  "community.column_settings.remote_only": "Solo remoto",
+  "compose_form.direct_message_warning": "Este toot solo será enviado a los usuarios mencionados.",
   "compose_form.direct_message_warning_learn_more": "Aprender mas",
-  "compose_form.hashtag_warning": "Esta publicación no se mostrará bajo hashtags porque no es público. Sólo se pueden buscar por hashtag las que son públicas.",
+  "compose_form.hashtag_warning": "Este toot no se mostrará bajo hashtags porque no es público. Sólo los toots públicos se pueden buscar por hashtag.",
   "compose_form.lock_disclaimer": "Tu cuenta no está bloqueada. Todos pueden seguirte para ver tus toots solo para seguidores.",
-  "compose_form.lock_disclaimer.lock": "bloqueada",
-  "compose_form.placeholder": "¿Qué rollo?",
+  "compose_form.lock_disclaimer.lock": "bloqueado",
+  "compose_form.placeholder": "¿En qué estás pensando?",
   "compose_form.poll.add_option": "Añadir una opción",
   "compose_form.poll.duration": "Duración de la encuesta",
-  "compose_form.poll.option_placeholder": "Opción {number}",
-  "compose_form.poll.remove_option": "Quitar esta opción",
-  "compose_form.poll.switch_to_multiple": "Modificar encuesta para permitir opciones múltiples",
-  "compose_form.poll.switch_to_single": "Modificar encuesta para permitir una sola opción",
-  "compose_form.publish": "Publicar",
-  "compose_form.publish_loud": "¡{publish}!",
-  "compose_form.sensitive.hide": "Marcar medio como sensible",
-  "compose_form.sensitive.marked": "Medio marcado como sensible",
-  "compose_form.sensitive.unmarked": "Medio no marcado como sensible",
-  "compose_form.spoiler.marked": "El texto está oculto detrás de la advertencia",
-  "compose_form.spoiler.unmarked": "El texto no está oculto",
-  "compose_form.spoiler_placeholder": "Escribe tu advertencia aquí",
+  "compose_form.poll.option_placeholder": "Elección {number}",
+  "compose_form.poll.remove_option": "Eliminar esta opción",
+  "compose_form.poll.switch_to_multiple": "Modificar encuesta para permitir múltiples opciones",
+  "compose_form.poll.switch_to_single": "Modificar encuesta para permitir una única opción",
+  "compose_form.publish": "Tootear",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive.hide": "Marcar multimedia como sensible",
+  "compose_form.sensitive.marked": "Material marcado como sensible",
+  "compose_form.sensitive.unmarked": "Material no marcado como sensible",
+  "compose_form.spoiler.marked": "Texto oculto tras la advertencia",
+  "compose_form.spoiler.unmarked": "Texto no oculto",
+  "compose_form.spoiler_placeholder": "Advertencia de contenido",
   "confirmation_modal.cancel": "Cancelar",
-  "confirmations.block.block_and_report": "Bloquear y denunciar",
+  "confirmations.block.block_and_report": "Bloquear y Reportar",
   "confirmations.block.confirm": "Bloquear",
-  "confirmations.block.message": "¿Estás segurx que quieres bloquear a {name}?",
+  "confirmations.block.message": "¿Estás seguro de que quieres bloquear a {name}?",
   "confirmations.delete.confirm": "Eliminar",
-  "confirmations.delete.message": "¿Estás segurx que quieres eliminar esta publicación?",
+  "confirmations.delete.message": "¿Estás seguro de que quieres borrar este toot?",
   "confirmations.delete_list.confirm": "Eliminar",
-  "confirmations.delete_list.message": "¿Estás segurx que quieres eliminar esta lista permanentemente?",
-  "confirmations.domain_block.confirm": "Bloquear dominio entero",
+  "confirmations.delete_list.message": "¿Seguro que quieres borrar esta lista permanentemente?",
+  "confirmations.domain_block.confirm": "Ocultar dominio entero",
   "confirmations.domain_block.message": "¿Seguro de que quieres bloquear al dominio {domain} entero? En general unos cuantos bloqueos y silenciados concretos es suficiente y preferible.",
   "confirmations.logout.confirm": "Cerrar sesión",
-  "confirmations.logout.message": "¿Estás segurx que quieres cerrar sesión?",
+  "confirmations.logout.message": "¿Estás seguro de querer cerrar la sesión?",
   "confirmations.mute.confirm": "Silenciar",
   "confirmations.mute.explanation": "Esto esconderá las publicaciones de ellos y en las que los has mencionado, pero les permitirá ver tus mensajes y seguirte.",
-  "confirmations.mute.message": "¿Estás segurx que quieres silenciar a {name}?",
-  "confirmations.redraft.confirm": "Eliminar y volver a redactar",
+  "confirmations.mute.message": "¿Estás seguro de que quieres silenciar a {name}?",
+  "confirmations.redraft.confirm": "Borrar y volver a borrador",
   "confirmations.redraft.message": "¿Estás seguro de que quieres eliminar este toot y convertirlo en borrador? Perderás todas las respuestas, retoots y favoritos asociados a él, y las respuestas a la publicación original quedarán huérfanas.",
   "confirmations.reply.confirm": "Responder",
   "confirmations.reply.message": "Responder sobrescribirá el mensaje que estás escribiendo. ¿Estás seguro de que deseas continuar?",
   "confirmations.unfollow.confirm": "Dejar de seguir",
-  "confirmations.unfollow.message": "¿Estás seguro que quieres dejar de seguir a {name}?",
-  "conversation.delete": "Eliminar conversación",
+  "confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?",
+  "conversation.delete": "Borrar conversación",
   "conversation.mark_as_read": "Marcar como leído",
   "conversation.open": "Ver conversación",
   "conversation.with": "Con {names}",
@@ -151,22 +151,22 @@
   "emoji_button.travel": "Viajes y lugares",
   "empty_column.account_timeline": "¡No hay toots aquí!",
   "empty_column.account_unavailable": "Perfil no disponible",
-  "empty_column.blocks": "No has bloqueado a nadie aún.",
-  "empty_column.bookmarked_statuses": "Todavía no tienes publicaciones guardadas en \"Marcadores\". Cuando guardes una, se mostrará quí.",
+  "empty_column.blocks": "Aún no has bloqueado a ningún usuario.",
+  "empty_column.bookmarked_statuses": "Aún no tienes ningún toot guardado como marcador. Cuando guardes uno, se mostrará aquí.",
   "empty_column.community": "La línea de tiempo local está vacía. ¡Escribe algo para empezar la fiesta!",
-  "empty_column.direct": "Todavía no tienes mensaje directos. Cuando envíes o recibas uno, se mostrará aquí.",
+  "empty_column.direct": "Aún no tienes ningún mensaje directo. Cuando envíes o recibas uno, se mostrará aquí.",
   "empty_column.domain_blocks": "Todavía no hay dominios ocultos.",
-  "empty_column.favourited_statuses": "Todavía no tienes publicaciones favoritas. Cuando marques un favorito, se mostrará aquí.",
+  "empty_column.favourited_statuses": "Aún no tienes toots preferidos. Cuando marques uno como favorito, aparecerá aquí.",
   "empty_column.favourites": "Nadie ha marcado este toot como preferido. Cuando alguien lo haga, aparecerá aquí.",
-  "empty_column.follow_requests": "Todavía no tienes solicitudes de seguimiento. Cuando recibas una, se mostrará aquí.",
+  "empty_column.follow_requests": "No tienes ninguna petición de seguidor. Cuando recibas una, se mostrará aquí.",
   "empty_column.hashtag": "No hay nada en este hashtag aún.",
-  "empty_column.home": "¡Tu línea de tiempo principal está vacía! Visita {public} o usa la búsqueda para comenzar y encontrar a otras personas.",
+  "empty_column.home": "No estás siguiendo a nadie aún. Visita {public} o haz búsquedas para empezar y conocer gente nueva.",
   "empty_column.home.public_timeline": "la línea de tiempo pública",
   "empty_column.list": "No hay nada en esta lista aún. Cuando miembros de esta lista publiquen nuevos estatus, estos aparecerán qui.",
-  "empty_column.lists": "Todavía no tienes listas. Cuando crees una, se mostrará aquí.",
-  "empty_column.mutes": "No has silenciado a nadie aún.",
-  "empty_column.notifications": "Todavía no tienes notificaciones. Interactúa con otras personas para iniciar la conversación.",
-  "empty_column.public": "¡No hay nada aquí! Escribe algo público, o manualmente sigue a alguien de otros servidores para irlo llenando",
+  "empty_column.lists": "No tienes ninguna lista. cuando crees una, se mostrará aquí.",
+  "empty_column.mutes": "Aún no has silenciado a ningún usuario.",
+  "empty_column.notifications": "No tienes ninguna notificación aún. Interactúa con otros para empezar una conversación.",
+  "empty_column.public": "¡No hay nada aquí! Escribe algo públicamente, o sigue usuarios de otras instancias manualmente para llenarlo",
   "error.unexpected_crash.explanation": "Debido a un error en nuestro código o a un problema de compatibilidad con el navegador, esta página no se ha podido mostrar correctamente.",
   "error.unexpected_crash.explanation_addons": "No se pudo mostrar correctamente esta página. Este error probablemente fue causado por un complemento del navegador web o por herramientas de traducción automática.",
   "error.unexpected_crash.next_steps": "Intenta actualizar la página. Si eso no ayuda, es posible que puedas usar Mastodon a través de otro navegador o aplicación nativa.",
@@ -183,7 +183,7 @@
   "getting_started.heading": "Primeros pasos",
   "getting_started.invite": "Invitar usuarios",
   "getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}.",
-  "getting_started.security": "Configuración de cuenta",
+  "getting_started.security": "Seguridad",
   "getting_started.terms": "Términos de servicio",
   "hashtag.column_header.tag_mode.all": "y {additional}",
   "hashtag.column_header.tag_mode.any": "o {additional}",
@@ -220,9 +220,9 @@
   "introduction.welcome.headline": "Primeros pasos",
   "introduction.welcome.text": "¡Bienvenido al fediverso! En unos momentos, podrás transmitir mensajes y hablar con tus amigos a través de una amplia variedad de servidores. Pero este servidor, {domain}, es especial, alberga tu perfil, así que recuerda su nombre.",
   "keyboard_shortcuts.back": "volver atrás",
-  "keyboard_shortcuts.blocked": "abre la lista de personas bloqueadas",
+  "keyboard_shortcuts.blocked": "abrir una lista de usuarios bloqueados",
   "keyboard_shortcuts.boost": "retootear",
-  "keyboard_shortcuts.column": "enfoca una publicación un una de las columnas",
+  "keyboard_shortcuts.column": "enfocar un estado en una de las columnas",
   "keyboard_shortcuts.compose": "enfocar el área de texto de redacción",
   "keyboard_shortcuts.description": "Descripción",
   "keyboard_shortcuts.direct": "abrir la columna de mensajes directos",
@@ -237,11 +237,11 @@
   "keyboard_shortcuts.legend": "para mostrar esta leyenda",
   "keyboard_shortcuts.local": "abrir el timeline local",
   "keyboard_shortcuts.mention": "para mencionar al autor",
-  "keyboard_shortcuts.muted": "abre la lista de personas silenciadas",
+  "keyboard_shortcuts.muted": "abrir la lista de usuarios silenciados",
   "keyboard_shortcuts.my_profile": "abrir tu perfil",
   "keyboard_shortcuts.notifications": "abrir la columna de notificaciones",
   "keyboard_shortcuts.open_media": "para abrir archivos multimedia",
-  "keyboard_shortcuts.pinned": "abre la lista de publicaciones fijadas",
+  "keyboard_shortcuts.pinned": "abrir la lista de toots destacados",
   "keyboard_shortcuts.profile": "abrir el perfil del autor",
   "keyboard_shortcuts.reply": "para responder",
   "keyboard_shortcuts.requests": "abrir la lista de peticiones de seguidores",
@@ -266,7 +266,7 @@
   "lists.edit.submit": "Cambiar título",
   "lists.new.create": "Añadir lista",
   "lists.new.title_placeholder": "Título de la nueva lista",
-  "lists.replies_policy.all_replies": "Cualquier persona seguida",
+  "lists.replies_policy.all_replies": "Cualquier usuario al que sigas",
   "lists.replies_policy.list_replies": "Miembros de la lista",
   "lists.replies_policy.no_replies": "Nadie",
   "lists.replies_policy.title": "Mostrar respuestas a:",
@@ -278,10 +278,10 @@
   "missing_indicator.label": "No encontrado",
   "missing_indicator.sublabel": "No se encontró este recurso",
   "mute_modal.duration": "Duración",
-  "mute_modal.hide_notifications": "¿Ocultar notificaciones de esta persona?",
+  "mute_modal.hide_notifications": "Ocultar notificaciones de este usuario?",
   "mute_modal.indefinite": "Indefinida",
   "navigation_bar.apps": "Aplicaciones móviles",
-  "navigation_bar.blocks": "Personas bloqueadas",
+  "navigation_bar.blocks": "Usuarios bloqueados",
   "navigation_bar.bookmarks": "Marcadores",
   "navigation_bar.community_timeline": "Historia local",
   "navigation_bar.compose": "Escribir un nuevo toot",
@@ -297,11 +297,11 @@
   "navigation_bar.keyboard_shortcuts": "Atajos",
   "navigation_bar.lists": "Listas",
   "navigation_bar.logout": "Cerrar sesión",
-  "navigation_bar.mutes": "Personas silenciadas",
+  "navigation_bar.mutes": "Usuarios silenciados",
   "navigation_bar.personal": "Personal",
-  "navigation_bar.pins": "Publicaciones fijadas",
+  "navigation_bar.pins": "Toots fijados",
   "navigation_bar.preferences": "Preferencias",
-  "navigation_bar.public_timeline": "Línea de tiempo federada",
+  "navigation_bar.public_timeline": "Historia federada",
   "navigation_bar.security": "Seguridad",
   "notification.favourite": "{name} marcó tu estado como favorito",
   "notification.follow": "{name} te empezó a seguir",
@@ -351,10 +351,10 @@
   "poll_button.add_poll": "Añadir una encuesta",
   "poll_button.remove_poll": "Eliminar encuesta",
   "privacy.change": "Ajustar privacidad",
-  "privacy.direct.long": "Sólo visible para las personas mencionadas",
+  "privacy.direct.long": "Sólo mostrar a los usuarios mencionados",
   "privacy.direct.short": "Directo",
   "privacy.private.long": "Sólo mostrar a seguidores",
-  "privacy.private.short": "Sólo a seguidorxs",
+  "privacy.private.short": "Privado",
   "privacy.public.long": "Mostrar en la historia federada",
   "privacy.public.short": "Público",
   "privacy.unlisted.long": "No mostrar en la historia federada",
@@ -377,20 +377,20 @@
   "report.target": "Reportando",
   "search.placeholder": "Buscar",
   "search_popout.search_format": "Formato de búsqueda avanzada",
-  "search_popout.tips.full_text": "Las búsquedas de texto recuperan publicaciones que hayas escrito, marcado como favoritas, compartido, o en las que te hayan mencionado, así como personas, nombres y hashtags.",
+  "search_popout.tips.full_text": "Búsquedas de texto recuperan posts que has escrito, marcado como favoritos, retooteado o en los que has sido mencionado, así como usuarios, nombres y hashtags.",
   "search_popout.tips.hashtag": "etiqueta",
-  "search_popout.tips.status": "publicación",
-  "search_popout.tips.text": "Las búsquedas de texto devuelven los nombres mostrados, nombres de usuarix y hashtags que coincidan",
-  "search_popout.tips.user": "usuarix",
+  "search_popout.tips.status": "toot",
+  "search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag",
+  "search_popout.tips.user": "usuario",
   "search_results.accounts": "Gente",
   "search_results.hashtags": "Etiquetas",
-  "search_results.statuses": "Publicaciones",
+  "search_results.statuses": "Toots",
   "search_results.statuses_fts_disabled": "Buscar toots por su contenido no está disponible en este servidor de Mastodon.",
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "status.admin_account": "Abrir interfaz de moderación para @{name}",
   "status.admin_status": "Abrir este estado en la interfaz de moderación",
   "status.block": "Bloquear a @{name}",
-  "status.bookmark": "Marcar",
+  "status.bookmark": "Marcador",
   "status.cancel_reblog_private": "Eliminar retoot",
   "status.cannot_reblog": "Este toot no puede retootearse",
   "status.copy": "Copiar enlace al estado",
@@ -408,14 +408,14 @@
   "status.mute_conversation": "Silenciar conversación",
   "status.open": "Expandir estado",
   "status.pin": "Fijar",
-  "status.pinned": "Publicación fijada",
+  "status.pinned": "Toot fijado",
   "status.read_more": "Leer más",
   "status.reblog": "Retootear",
   "status.reblog_private": "Implusar a la audiencia original",
   "status.reblogged_by": "Retooteado por {name}",
   "status.reblogs.empty": "Nadie retooteó este toot todavía. Cuando alguien lo haga, aparecerá aquí.",
   "status.redraft": "Borrar y volver a borrador",
-  "status.remove_bookmark": "Quitar marcador",
+  "status.remove_bookmark": "Eliminar marcador",
   "status.reply": "Responder",
   "status.replyAll": "Responder al hilo",
   "status.report": "Reportar",
@@ -428,7 +428,7 @@
   "status.show_thread": "Mostrar hilo",
   "status.uncached_media_warning": "No disponible",
   "status.unmute_conversation": "Dejar de silenciar conversación",
-  "status.unpin": "Desfijar",
+  "status.unpin": "Dejar de fijar",
   "suggestions.dismiss": "Descartar sugerencia",
   "suggestions.header": "Es posible que te interese…",
   "tabs_bar.federated_timeline": "Federado",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 79b9966db..5a2d5a54e 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -254,8 +254,8 @@
   "keyboard_shortcuts.unfocus": "убрать фокус с поля ввода/поиска",
   "keyboard_shortcuts.up": "вверх по списку",
   "lightbox.close": "Закрыть",
-  "lightbox.compress": "Compress image view box",
-  "lightbox.expand": "Expand image view box",
+  "lightbox.compress": "Сжать окно просмотра изображений",
+  "lightbox.expand": "Развернуть окно просмотра изображений",
   "lightbox.next": "Далее",
   "lightbox.previous": "Назад",
   "lightbox.view_context": "Контекст",
@@ -336,11 +336,11 @@
   "notifications.filter.statuses": "Обновления от людей, на которых вы подписаны",
   "notifications.group": "{count} уведомл.",
   "notifications.mark_as_read": "Отмечать все уведомления прочитанными",
-  "notifications.permission_denied": "Desktop notifications are unavailable due to previously denied browser permissions request",
-  "notifications.permission_denied_alert": "Desktop notifications can't be enabled, as browser permission has been denied before",
-  "notifications_permission_banner.enable": "Enable desktop notifications",
-  "notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
-  "notifications_permission_banner.title": "Never miss a thing",
+  "notifications.permission_denied": "Уведомления на рабочем столе недоступны из-за ранее отклонённого запроса разрешений браузера",
+  "notifications.permission_denied_alert": "Уведомления на рабочем столе не могут быть включены, так как раньше было отказано в разрешении браузера",
+  "notifications_permission_banner.enable": "Включить уведомления на рабочем столе",
+  "notifications_permission_banner.how_to_control": "Чтобы получать уведомления, когда Мастодон не открыт, включите уведомления рабочего стола. Вы можете точно управлять, какие типы взаимодействия генерируют уведомления рабочего стола с помощью кнопки {icon} выше, когда они включены.",
+  "notifications_permission_banner.title": "Ничего не пропустите",
   "picture_in_picture.restore": "Вернуть обратно",
   "poll.closed": "Завершён",
   "poll.refresh": "Обновить",
diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json
index bc8463d59..a8f5171c9 100644
--- a/app/javascript/mastodon/locales/sa.json
+++ b/app/javascript/mastodon/locales/sa.json
@@ -470,7 +470,7 @@
   "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.preparing_ocr": "Preparing OCR…",
   "upload_modal.preview_label": "Preview ({ratio})",
-  "upload_progress.label": "Uploading...",
+  "upload_progress.label": "Uploading…",
   "video.close": "Close video",
   "video.download": "Download file",
   "video.exit_fullscreen": "Exit full screen",
diff --git a/app/javascript/mastodon/locales/szl.json b/app/javascript/mastodon/locales/szl.json
index 2e57d620b..17ffe5519 100644
--- a/app/javascript/mastodon/locales/szl.json
+++ b/app/javascript/mastodon/locales/szl.json
@@ -470,7 +470,7 @@
   "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.preparing_ocr": "Preparing OCR…",
   "upload_modal.preview_label": "Preview ({ratio})",
-  "upload_progress.label": "Uploading...",
+  "upload_progress.label": "Uploading…",
   "video.close": "Close video",
   "video.download": "Download file",
   "video.exit_fullscreen": "Exit full screen",
diff --git a/app/javascript/mastodon/locales/tai.json b/app/javascript/mastodon/locales/tai.json
index 2e57d620b..17ffe5519 100644
--- a/app/javascript/mastodon/locales/tai.json
+++ b/app/javascript/mastodon/locales/tai.json
@@ -470,7 +470,7 @@
   "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.preparing_ocr": "Preparing OCR…",
   "upload_modal.preview_label": "Preview ({ratio})",
-  "upload_progress.label": "Uploading...",
+  "upload_progress.label": "Uploading…",
   "video.close": "Close video",
   "video.download": "Download file",
   "video.exit_fullscreen": "Exit full screen",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 47ce6f83f..650fbe806 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -103,7 +103,7 @@
   "compose_form.sensitive.unmarked": "{count, plural, one {Medya hassas olarak işaretlenmemiş} other {Medya hassas olarak işaretlenmemiş}}",
   "compose_form.spoiler.marked": "Metin uyarının arkasına gizlenir",
   "compose_form.spoiler.unmarked": "Metin gizli değil",
-  "compose_form.spoiler_placeholder": "İçerik uyarısı",
+  "compose_form.spoiler_placeholder": "Uyarınızı buraya yazın",
   "confirmation_modal.cancel": "İptal",
   "confirmations.block.block_and_report": "Engelle ve Bildir",
   "confirmations.block.confirm": "Engelle",
@@ -164,7 +164,7 @@
   "empty_column.home.public_timeline": "herkese açık zaman tüneli",
   "empty_column.list": "Bu listede henüz hiçbir şey yok.",
   "empty_column.lists": "Henüz listeniz yok. Liste oluşturduğunuzda burada görünecek.",
-  "empty_column.mutes": "Henüz hiçbir kullanıcıyı sessize almadınız.",
+  "empty_column.mutes": "Henüz bir kullanıcıyı sessize almadınız.",
   "empty_column.notifications": "Henüz bildiriminiz yok. Sohbete başlamak için başkalarıyla etkileşim kurun.",
   "empty_column.public": "Burada hiçbir şey yok! Herkese açık bir şeyler yazın veya burayı doldurmak için diğer sunuculardaki kullanıcıları takip edin",
   "error.unexpected_crash.explanation": "Bizim kodumuzdaki bir hatadan ya da tarayıcı uyumluluk sorunundan dolayı, bu sayfa düzgün görüntülenemedi.",
@@ -210,7 +210,7 @@
   "introduction.federation.local.headline": "Yerel",
   "introduction.federation.local.text": "Aynı sunucudaki kişilerin gönderileri yerel zaman tünelinde gözükecektir.",
   "introduction.interactions.action": "Öğreticiyi bitir!",
-  "introduction.interactions.favourite.headline": "Favori",
+  "introduction.interactions.favourite.headline": "Beğeni",
   "introduction.interactions.favourite.text": "Bir tootu favorilerinize alarak sonrası için saklayabilirsiniz ve yazara tootu beğendiğinizi söyleyebilirsiniz.",
   "introduction.interactions.reblog.headline": "Boostla",
   "introduction.interactions.reblog.text": "Başkalarının tootlarını boostlayarak onları kendi takipçilerinizle paylaşabillirsiniz.",
@@ -220,20 +220,20 @@
   "introduction.welcome.headline": "İlk adımlar",
   "introduction.welcome.text": "Krallığa hoş geldiniz! Az sonra, geniş bir sunucu yelpazesinde mesaj gönderip arkadaşlarınızla konuşabileceksiniz. Ama bu sunucu, {domain}, özel (profilinizi barındırır, bu yüzden adresini hatırlayın).",
   "keyboard_shortcuts.back": "geriye gitmek için",
-  "keyboard_shortcuts.blocked": "engelli kullanıcılar listesini açmak için",
+  "keyboard_shortcuts.blocked": "engellenen kullanıcılar listesini açmak için",
   "keyboard_shortcuts.boost": "boostlamak için",
   "keyboard_shortcuts.column": "sütunlardan birindeki duruma odaklanmak için",
   "keyboard_shortcuts.compose": "yazma alanına odaklanmak için",
   "keyboard_shortcuts.description": "Açıklama",
-  "keyboard_shortcuts.direct": "doğrudan mesajlar sütununu açmak için",
+  "keyboard_shortcuts.direct": "direkt mesajlar sütununu açmak için",
   "keyboard_shortcuts.down": "listede aşağıya inmek için",
   "keyboard_shortcuts.enter": "durumu açmak için",
-  "keyboard_shortcuts.favourite": "favorilere eklemek için",
+  "keyboard_shortcuts.favourite": "beğenmek için",
   "keyboard_shortcuts.favourites": "favoriler listesini açmak için",
   "keyboard_shortcuts.federated": "federe edilmiş zaman tünelini açmak için",
   "keyboard_shortcuts.heading": "Klavye kısayolları",
-  "keyboard_shortcuts.home": "ana sayfa zaman çizelgesini açmak için",
-  "keyboard_shortcuts.hotkey": "Kısatuş",
+  "keyboard_shortcuts.home": "anasayfa zaman çizelgesini açmak için",
+  "keyboard_shortcuts.hotkey": "Kısayol tuşu",
   "keyboard_shortcuts.legend": "bu efsaneyi görüntülemek için",
   "keyboard_shortcuts.local": "yerel zaman tünelini açmak için",
   "keyboard_shortcuts.mention": "yazardan bahsetmek için",
@@ -246,11 +246,11 @@
   "keyboard_shortcuts.reply": "yanıtlamak için",
   "keyboard_shortcuts.requests": "takip istekleri listesini açmak için",
   "keyboard_shortcuts.search": "aramaya odaklanmak için",
-  "keyboard_shortcuts.spoilers": "CW akışı göster/gizle",
-  "keyboard_shortcuts.start": "\"başlayın\" sütununu açmak için",
+  "keyboard_shortcuts.spoilers": "CW alanını göstermek/gizlemek için",
+  "keyboard_shortcuts.start": "\"başlarken\" sütununu açmak için",
   "keyboard_shortcuts.toggle_hidden": "CW'den önceki yazıyı göstermek/gizlemek için",
   "keyboard_shortcuts.toggle_sensitivity": "medyayı göstermek/gizlemek için",
-  "keyboard_shortcuts.toot": "yeni bir toot başlatmak için",
+  "keyboard_shortcuts.toot": "yepyeni bir toot başlatmak için",
   "keyboard_shortcuts.unfocus": "aramada bir gönderiye odaklanmamak için",
   "keyboard_shortcuts.up": "listede yukarıya çıkmak için",
   "lightbox.close": "Kapat",
@@ -314,8 +314,8 @@
   "notifications.clear": "Bildirimleri temizle",
   "notifications.clear_confirmation": "Tüm bildirimlerinizi kalıcı olarak temizlemek ister misiniz?",
   "notifications.column_settings.alert": "Masaüstü bildirimleri",
-  "notifications.column_settings.favourite": "Favoriler:",
-  "notifications.column_settings.filter_bar.advanced": "Tüm kategorileri göster",
+  "notifications.column_settings.favourite": "Beğeniler:",
+  "notifications.column_settings.filter_bar.advanced": "Tüm kategorileri görüntüle",
   "notifications.column_settings.filter_bar.category": "Hızlı filtre çubuğu",
   "notifications.column_settings.filter_bar.show": "Göster",
   "notifications.column_settings.follow": "Yeni takipçiler:",
@@ -324,12 +324,12 @@
   "notifications.column_settings.poll": "Anket sonuçları:",
   "notifications.column_settings.push": "Anlık bildirimler",
   "notifications.column_settings.reblog": "Boostlar:",
-  "notifications.column_settings.show": "Bildirimlerde göster",
+  "notifications.column_settings.show": "Sütunda göster",
   "notifications.column_settings.sound": "Ses çal",
-  "notifications.column_settings.status": "Yeni toot'lar:",
+  "notifications.column_settings.status": "Yeni tootlar:",
   "notifications.filter.all": "Tümü",
   "notifications.filter.boosts": "Boostlar",
-  "notifications.filter.favourites": "Favoriler",
+  "notifications.filter.favourites": "Beğeniler",
   "notifications.filter.follows": "Takip edilenler",
   "notifications.filter.mentions": "Bahsetmeler",
   "notifications.filter.polls": "Anket sonuçları",
@@ -349,19 +349,19 @@
   "poll.vote": "Oy ver",
   "poll.voted": "Bu cevap için oy kullandınız",
   "poll_button.add_poll": "Bir anket ekleyin",
-  "poll_button.remove_poll": "Anket kaldır",
-  "privacy.change": "Gönderi gizliliğini ayarla",
-  "privacy.direct.long": "Sadece bahsedilen kişilere gönder",
-  "privacy.direct.short": "Doğrudan",
-  "privacy.private.long": "Sadece takipçilerime gönder",
+  "poll_button.remove_poll": "Anketi kaldır",
+  "privacy.change": "Toot gizliliğini ayarlayın",
+  "privacy.direct.long": "Sadece bahsedilen kullanıcılar için görünür",
+  "privacy.direct.short": "Direkt",
+  "privacy.private.long": "Sadece takipçiler için görünür",
   "privacy.private.short": "Sadece takipçiler",
-  "privacy.public.long": "Herkese açık zaman tüneline gönder",
+  "privacy.public.long": "Herkese görünür, herkese açık zaman çizelgelerinde gösterilir",
   "privacy.public.short": "Herkese açık",
-  "privacy.unlisted.long": "Herkese açık zaman tüneline gönderme",
+  "privacy.unlisted.long": "Herkese görünür, ancak genel zaman çizelgelerinde gösterilmez",
   "privacy.unlisted.short": "Listelenmemiş",
   "refresh": "Yenile",
   "regeneration_indicator.label": "Yükleniyor…",
-  "regeneration_indicator.sublabel": "Ev akışınız hazırlanıyor!",
+  "regeneration_indicator.sublabel": "Ana akışınız hazırlanıyor!",
   "relative_time.days": "{number}g",
   "relative_time.hours": "{number}sa",
   "relative_time.just_now": "şimdi",
@@ -370,7 +370,7 @@
   "relative_time.today": "bugün",
   "reply_indicator.cancel": "İptal",
   "report.forward": "{target} ilet",
-  "report.forward_hint": "Bu hesap başka bir sunucudan. Anonimleştirilmiş bir rapor oraya da gönderilsin mi?",
+  "report.forward_hint": "Hesap başka bir sunucudan. Raporun anonim bir kopyası da oraya gönderilsin mi?",
   "report.hint": "Bu rapor sunucu moderatörlerine gönderilecek. Bu hesabı neden bildirdiğiniz hakkında bilgi verebirsiniz:",
   "report.placeholder": "Ek yorumlar",
   "report.submit": "Gönder",
@@ -378,7 +378,7 @@
   "search.placeholder": "Ara",
   "search_popout.search_format": "Gelişmiş arama biçimi",
   "search_popout.tips.full_text": "Basit metin yazdığınız, tercih ettiğiniz, boostladığınız veya bunlardan bahsettiğiniz tootların yanı sıra kullanıcı adlarını, görünen adları ve hashtag'leri eşleştiren tootları döndürür.",
-  "search_popout.tips.hashtag": "etiketler",
+  "search_popout.tips.hashtag": "etiket",
   "search_popout.tips.status": "toot",
   "search_popout.tips.text": "Basit metin, eşleşen görünen adları, kullanıcı adlarını ve hashtag'leri döndürür",
   "search_popout.tips.user": "kullanıcı",
@@ -396,36 +396,36 @@
   "status.copy": "Bağlantı durumunu kopyala",
   "status.delete": "Sil",
   "status.detailed_status": "Ayrıntılı sohbet görünümü",
-  "status.direct": "@{name}'e gönder",
+  "status.direct": "@{name} adlı kişiye direkt mesaj",
   "status.embed": "Gömülü",
   "status.favourite": "Beğen",
   "status.filtered": "Filtrelenmiş",
-  "status.load_more": "Daha fazla",
+  "status.load_more": "Daha fazlasını yükle",
   "status.media_hidden": "Gizli görsel",
-  "status.mention": "Bahset : @{name}",
+  "status.mention": "@{name} kişisinden bahset",
   "status.more": "Daha fazla",
-  "status.mute": "Sustur : @{name}",
+  "status.mute": "@{name} kişisini sessize al",
   "status.mute_conversation": "Sohbeti sessize al",
-  "status.open": "Bu gönderiyi genişlet",
+  "status.open": "Bu tootu genişlet",
   "status.pin": "Profile sabitle",
   "status.pinned": "Sabitlenmiş toot",
-  "status.read_more": "Daha dazla oku",
+  "status.read_more": "Devamını okuyun",
   "status.reblog": "Boostla",
   "status.reblog_private": "Orijinal görünürlük ile boostla",
   "status.reblogged_by": "{name} boostladı",
   "status.reblogs.empty": "Henüz kimse bu tootu boostlamadı. Biri yaptığında burada görünecek.",
-  "status.redraft": "Sil & tekrar taslakla",
+  "status.redraft": "Sil ve yeniden taslak yap",
   "status.remove_bookmark": "Yer imini kaldır",
   "status.reply": "Yanıtla",
   "status.replyAll": "Konuyu yanıtla",
-  "status.report": "@{name}'i raporla",
+  "status.report": "@{name} adlı kişiyi bildir",
   "status.sensitive_warning": "Hassas içerik",
   "status.share": "Paylaş",
   "status.show_less": "Daha az göster",
   "status.show_less_all": "Hepsi için daha az göster",
-  "status.show_more": "Daha fazla göster",
+  "status.show_more": "Daha fazlasını göster",
   "status.show_more_all": "Hepsi için daha fazla göster",
-  "status.show_thread": "Mesaj dizisini göster",
+  "status.show_thread": "Konuyu göster",
   "status.uncached_media_warning": "Mevcut değil",
   "status.unmute_conversation": "Sohbet sesini aç",
   "status.unpin": "Profilden sabitlemeyi kaldır",
@@ -446,22 +446,22 @@
   "timeline_hint.resources.follows": "Takip Edilenler",
   "timeline_hint.resources.statuses": "Eski tootlar",
   "trends.counter_by_accounts": "{count, plural, one {{counter} kişi} other {{counter} kişi}} konuşuyor",
-  "trends.trending_now": "Şu an popüler",
-  "ui.beforeunload": "Mastodon'dan ayrılırsanız taslağınız kaybolacak.",
-  "units.short.billion": "{count}Mia",
-  "units.short.million": "{count}M",
-  "units.short.thousand": "{count}B",
+  "trends.trending_now": "Şu an gündemde",
+  "ui.beforeunload": "Mastodon'u terk ederseniz taslağınız kaybolacak.",
+  "units.short.billion": "{count}Mr",
+  "units.short.million": "{count}Mn",
+  "units.short.thousand": "{count}Mn",
   "upload_area.title": "Karşıya yükleme için sürükle bırak yapınız",
-  "upload_button.label": "Görsel ekle",
+  "upload_button.label": "Resim, video veya ses dosyası ekleyin",
   "upload_error.limit": "Dosya yükleme sınırı aşıldı.",
   "upload_error.poll": "Anketlerde dosya yüklemesine izin verilmez.",
   "upload_form.audio_description": "İşitme kaybı olan kişiler için tarif edin",
   "upload_form.description": "Görme engelliler için açıklama",
   "upload_form.edit": "Düzenle",
-  "upload_form.thumbnail": "Küçük Resimi Değiştir",
-  "upload_form.undo": "Geri al",
+  "upload_form.thumbnail": "Küçük resmi değiştir",
+  "upload_form.undo": "Sil",
   "upload_form.video_description": "İşitme kaybı veya görme engeli olan kişiler için tarif edin",
-  "upload_modal.analyzing_picture": "Resmi analiz ediyor…",
+  "upload_modal.analyzing_picture": "Resim analiz ediliyor…",
   "upload_modal.apply": "Uygula",
   "upload_modal.choose_image": "Resim seç",
   "upload_modal.description_placeholder": "Pijamalı hasta yağız şoföre çabucak güvendi",
@@ -477,7 +477,7 @@
   "video.expand": "Videoyu genişlet",
   "video.fullscreen": "Tam ekran",
   "video.hide": "Videoyu gizle",
-  "video.mute": "Sesi kıs",
+  "video.mute": "Sesi sustur",
   "video.pause": "Duraklat",
   "video.play": "Oynat",
   "video.unmute": "Sesi aç"
diff --git a/app/javascript/mastodon/locales/ug.json b/app/javascript/mastodon/locales/ug.json
index 2e57d620b..17ffe5519 100644
--- a/app/javascript/mastodon/locales/ug.json
+++ b/app/javascript/mastodon/locales/ug.json
@@ -470,7 +470,7 @@
   "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.preparing_ocr": "Preparing OCR…",
   "upload_modal.preview_label": "Preview ({ratio})",
-  "upload_progress.label": "Uploading...",
+  "upload_progress.label": "Uploading…",
   "video.close": "Close video",
   "video.download": "Download file",
   "video.exit_fullscreen": "Exit full screen",
diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json
index e9d9a42d2..5a3988ce8 100644
--- a/app/javascript/mastodon/locales/vi.json
+++ b/app/javascript/mastodon/locales/vi.json
@@ -272,7 +272,7 @@
   "lists.replies_policy.title": "Cho phép bình luận với:",
   "lists.search": "Tìm kiếm những người mà bạn quan tâm",
   "lists.subheading": "Danh sách của bạn",
-  "load_pending": "{count, plural, one {# tút} other {# tút}}",
+  "load_pending": "{count, plural, one {# tút mới} other {# tút mới}}",
   "loading_indicator.label": "Chờ xíu...",
   "media_gallery.toggle_visible": "Ẩn {number, plural, one {ảnh} other {ảnh}}",
   "missing_indicator.label": "Không tìm thấy",
diff --git a/app/javascript/mastodon/locales/zgh.json b/app/javascript/mastodon/locales/zgh.json
index 2e57d620b..b877d76b4 100644
--- a/app/javascript/mastodon/locales/zgh.json
+++ b/app/javascript/mastodon/locales/zgh.json
@@ -2,8 +2,8 @@
   "account.account_note_header": "Note",
   "account.add_or_remove_from_list": "Add or Remove from lists",
   "account.badges.bot": "Bot",
-  "account.badges.group": "Group",
-  "account.block": "Block @{name}",
+  "account.badges.group": "ⵜⴰⵔⴰⴱⴱⵓⵜ",
+  "account.block": "ⴳⴷⵍ @{name}",
   "account.block_domain": "Block domain {domain}",
   "account.blocked": "Blocked",
   "account.browse_more_on_origin_server": "Browse more on the original profile",
@@ -11,10 +11,10 @@
   "account.direct": "Direct message @{name}",
   "account.disable_notifications": "Stop notifying me when @{name} posts",
   "account.domain_blocked": "Domain blocked",
-  "account.edit_profile": "Edit profile",
+  "account.edit_profile": "ⵙⵏⴼⵍ ⵉⴼⵔⵙ",
   "account.enable_notifications": "Notify me when @{name} posts",
   "account.endorse": "Feature on profile",
-  "account.follow": "Follow",
+  "account.follow": "ⴹⴼⵕ",
   "account.followers": "Followers",
   "account.followers.empty": "No one follows this user yet.",
   "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}",
@@ -56,7 +56,7 @@
   "bundle_column_error.body": "Something went wrong while loading this component.",
   "bundle_column_error.retry": "Try again",
   "bundle_column_error.title": "Network error",
-  "bundle_modal_error.close": "Close",
+  "bundle_modal_error.close": "ⵔⴳⵍ",
   "bundle_modal_error.message": "Something went wrong while loading this component.",
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Blocked users",
@@ -67,8 +67,8 @@
   "column.domain_blocks": "Blocked domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
-  "column.home": "Home",
-  "column.lists": "Lists",
+  "column.home": "ⴰⵙⵏⵓⴱⴳ",
+  "column.lists": "ⵜⵉⵍⴳⴰⵎⵉⵏ",
   "column.mutes": "Muted users",
   "column.notifications": "Notifications",
   "column.pins": "Pinned toot",
@@ -80,7 +80,7 @@
   "column_header.pin": "Pin",
   "column_header.show_settings": "Show settings",
   "column_header.unpin": "Unpin",
-  "column_subheading.settings": "Settings",
+  "column_subheading.settings": "ⵜⵉⵙⵖⴰⵍ",
   "community.column_settings.local_only": "Local only",
   "community.column_settings.media_only": "Media only",
   "community.column_settings.remote_only": "Remote only",
@@ -108,9 +108,9 @@
   "confirmations.block.block_and_report": "Block & Report",
   "confirmations.block.confirm": "Block",
   "confirmations.block.message": "Are you sure you want to block {name}?",
-  "confirmations.delete.confirm": "Delete",
+  "confirmations.delete.confirm": "ⴽⴽⵙ",
   "confirmations.delete.message": "Are you sure you want to delete this status?",
-  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.confirm": "ⴽⴽⵙ",
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "Hide entire domain",
   "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
@@ -121,7 +121,7 @@
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
   "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
-  "confirmations.reply.confirm": "Reply",
+  "confirmations.reply.confirm": "ⵔⴰⵔ",
   "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.unfollow.message": "Are you sure you want to unfollow {name}?",
@@ -137,7 +137,7 @@
   "embed.preview": "Here is what it will look like:",
   "emoji_button.activity": "Activity",
   "emoji_button.custom": "Custom",
-  "emoji_button.flags": "Flags",
+  "emoji_button.flags": "ⵉⵛⵏⵢⴰⵍⵏ",
   "emoji_button.food": "Food & Drink",
   "emoji_button.label": "Insert emoji",
   "emoji_button.nature": "Nature",
@@ -205,7 +205,7 @@
   "introduction.federation.action": "Next",
   "introduction.federation.federated.headline": "Federated",
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
-  "introduction.federation.home.headline": "Home",
+  "introduction.federation.home.headline": "ⴰⵙⵏⵓⴱⴳ",
   "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
   "introduction.federation.local.headline": "Local",
   "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
@@ -214,7 +214,7 @@
   "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.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.headline": "ⵔⴰⵔ",
   "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.headline": "First steps",
@@ -253,15 +253,15 @@
   "keyboard_shortcuts.toot": "to start a brand new toot",
   "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
   "keyboard_shortcuts.up": "to move up in the list",
-  "lightbox.close": "Close",
+  "lightbox.close": "ⵔⴳⵍ",
   "lightbox.compress": "Compress image view box",
   "lightbox.expand": "Expand image view box",
   "lightbox.next": "Next",
   "lightbox.previous": "Previous",
   "lightbox.view_context": "View context",
-  "lists.account.add": "Add to list",
-  "lists.account.remove": "Remove from list",
-  "lists.delete": "Delete list",
+  "lists.account.add": "ⵔⵏⵓ ⵖⵔ ⵜⵍⴳⴰⵎⵜ",
+  "lists.account.remove": "ⴽⴽⵙ ⵙⴳ ⵜⵍⴳⴰⵎⵜ",
+  "lists.delete": "ⴽⴽⵙ ⵜⴰⵍⴳⴰⵎⵜ",
   "lists.edit": "Edit list",
   "lists.edit.submit": "Change title",
   "lists.new.create": "Add list",
@@ -348,7 +348,7 @@
   "poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
   "poll.vote": "Vote",
   "poll.voted": "You voted for this answer",
-  "poll_button.add_poll": "Add a poll",
+  "poll_button.add_poll": "ⵔⵏⵓ ⵢⴰⵏ ⵢⵉⴷⵣ",
   "poll_button.remove_poll": "Remove poll",
   "privacy.change": "Adjust status privacy",
   "privacy.direct.long": "Visible for mentioned users only",
@@ -360,11 +360,11 @@
   "privacy.unlisted.long": "Visible for all, but not in public timelines",
   "privacy.unlisted.short": "Unlisted",
   "refresh": "Refresh",
-  "regeneration_indicator.label": "Loading…",
+  "regeneration_indicator.label": "ⴰⵣⴷⴰⵎ…",
   "regeneration_indicator.sublabel": "Your home feed is being prepared!",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
-  "relative_time.just_now": "now",
+  "relative_time.just_now": "ⴷⵖⵉ",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "relative_time.today": "today",
@@ -373,9 +373,9 @@
   "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
   "report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:",
   "report.placeholder": "Additional comments",
-  "report.submit": "Submit",
+  "report.submit": "ⴰⵣⵏ",
   "report.target": "Report {target}",
-  "search.placeholder": "Search",
+  "search.placeholder": "ⵔⵣⵓ",
   "search_popout.search_format": "Advanced search format",
   "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
   "search_popout.tips.hashtag": "hashtag",
@@ -394,7 +394,7 @@
   "status.cancel_reblog_private": "Unboost",
   "status.cannot_reblog": "This post cannot be boosted",
   "status.copy": "Copy link to status",
-  "status.delete": "Delete",
+  "status.delete": "ⴽⴽⵙ",
   "status.detailed_status": "Detailed conversation view",
   "status.direct": "Direct message @{name}",
   "status.embed": "Embed",
@@ -409,14 +409,14 @@
   "status.open": "Expand this status",
   "status.pin": "Pin on profile",
   "status.pinned": "Pinned toot",
-  "status.read_more": "Read more",
+  "status.read_more": "ⵖⵔ ⵓⴳⴳⴰⵔ",
   "status.reblog": "Boost",
   "status.reblog_private": "Boost with original visibility",
   "status.reblogged_by": "{name} boosted",
   "status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
   "status.redraft": "Delete & re-draft",
   "status.remove_bookmark": "Remove bookmark",
-  "status.reply": "Reply",
+  "status.reply": "ⵔⴰⵔ",
   "status.replyAll": "Reply to thread",
   "status.report": "Report @{name}",
   "status.sensitive_warning": "Sensitive content",
@@ -432,10 +432,10 @@
   "suggestions.dismiss": "Dismiss suggestion",
   "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federated",
-  "tabs_bar.home": "Home",
+  "tabs_bar.home": "ⴰⵙⵏⵓⴱⴳ",
   "tabs_bar.local_timeline": "Local",
-  "tabs_bar.notifications": "Notifications",
-  "tabs_bar.search": "Search",
+  "tabs_bar.notifications": "ⵜⵉⵏⵖⵎⵉⵙⵉⵏ",
+  "tabs_bar.search": "ⵔⵣⵓ",
   "time_remaining.days": "{number, plural, one {# day} other {# days}} left",
   "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
   "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
@@ -457,9 +457,9 @@
   "upload_error.poll": "File upload not allowed with polls.",
   "upload_form.audio_description": "Describe for people with hearing loss",
   "upload_form.description": "Describe for the visually impaired",
-  "upload_form.edit": "Edit",
+  "upload_form.edit": "ⵙⵏⴼⵍ",
   "upload_form.thumbnail": "Change thumbnail",
-  "upload_form.undo": "Delete",
+  "upload_form.undo": "ⴽⴽⵙ",
   "upload_form.video_description": "Describe for people with hearing loss or visual impairment",
   "upload_modal.analyzing_picture": "Analyzing picture…",
   "upload_modal.apply": "Apply",
@@ -470,9 +470,9 @@
   "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.preparing_ocr": "Preparing OCR…",
   "upload_modal.preview_label": "Preview ({ratio})",
-  "upload_progress.label": "Uploading...",
-  "video.close": "Close video",
-  "video.download": "Download file",
+  "upload_progress.label": "Uploading…",
+  "video.close": "ⵔⴳⵍ ⴰⴼⵉⴷⵢⵓ",
+  "video.download": "ⴰⴳⵎ ⴰⴼⴰⵢⵍⵓ",
   "video.exit_fullscreen": "Exit full screen",
   "video.expand": "Expand video",
   "video.fullscreen": "Full screen",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index ac189fda1..956067e6f 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -170,7 +170,7 @@
   "error.unexpected_crash.explanation": "此页面无法正确显示,这可能是因为我们的代码中有错误,也可能是因为浏览器兼容问题。",
   "error.unexpected_crash.explanation_addons": "此页面无法正确显示,这个错误很可能是由浏览器附加组件或自动翻译工具造成的。",
   "error.unexpected_crash.next_steps": "刷新一下页面试试。如果没用,您可以换个浏览器或者用本地应用。",
-  "error.unexpected_crash.next_steps_addons": "请尝试禁用它们并刷新页面。如果没有帮助,您仍可以尝试使用其他浏览器或原生应用来使用 Mastodon。",
+  "error.unexpected_crash.next_steps_addons": "请尝试禁用它们并刷新页面。如果没有帮助,你仍可以尝试使用其他浏览器或原生应用来使用 Mastodon。",
   "errors.unexpected_crash.copy_stacktrace": "把堆栈跟踪信息复制到剪贴板",
   "errors.unexpected_crash.report_issue": "报告问题",
   "follow_request.authorize": "同意",
@@ -333,7 +333,7 @@
   "notifications.filter.follows": "关注",
   "notifications.filter.mentions": "提及",
   "notifications.filter.polls": "投票结果",
-  "notifications.filter.statuses": "您关注的人的动态",
+  "notifications.filter.statuses": "你关注的人的动态",
   "notifications.group": "{count} 条通知",
   "notifications.mark_as_read": "将所有通知标为已读",
   "notifications.permission_denied": "由于权限被拒绝,无法启用桌面通知。",
diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js
index 1d4874717..46a9d5376 100644
--- a/app/javascript/mastodon/reducers/notifications.js
+++ b/app/javascript/mastodon/reducers/notifications.js
@@ -12,6 +12,7 @@ import {
   NOTIFICATIONS_MARK_AS_READ,
   NOTIFICATIONS_SET_BROWSER_SUPPORT,
   NOTIFICATIONS_SET_BROWSER_PERMISSION,
+  NOTIFICATIONS_DISMISS_BROWSER_PERMISSION,
 } from '../actions/notifications';
 import {
   ACCOUNT_BLOCK_SUCCESS,
@@ -250,6 +251,8 @@ export default function notifications(state = initialState, action) {
     return state.set('browserSupport', action.value);
   case NOTIFICATIONS_SET_BROWSER_PERMISSION:
     return state.set('browserPermission', action.value);
+  case NOTIFICATIONS_DISMISS_BROWSER_PERMISSION:
+    return state.set('browserPermission', 'denied');
   default:
     return state;
   }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 46a790a7e..9c4e6d08f 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -7204,6 +7204,13 @@ noscript {
   flex-direction: column;
   align-items: center;
   justify-content: center;
+  position: relative;
+
+  &__close {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+  }
 
   h2 {
     font-size: 16px;
diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss
index 8602c3dde..f463419c8 100644
--- a/app/javascript/styles/mastodon/variables.scss
+++ b/app/javascript/styles/mastodon/variables.scss
@@ -36,6 +36,8 @@ $dark-text-color: $ui-base-lighter-color !default;
 $secondary-text-color: $ui-secondary-color !default;
 $highlight-text-color: $ui-highlight-color !default;
 $action-button-color: $ui-base-lighter-color !default;
+$passive-text-color: $gold-star !default;
+$active-passive-text-color: $success-green !default;
 // For texts on inverted backgrounds
 $inverted-text-color: $ui-base-color !default;
 $lighter-text-color: $ui-base-lighter-color !default;
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index 47e02d41d..5ee4d104b 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -446,6 +446,26 @@
     vertical-align: initial !important;
   }
 
+  &__interrelationships {
+    width: 21px;
+  }
+
+  .fa {
+    font-size: 16px;
+
+    &.active {
+      color: $highlight-text-color;
+    }
+
+    &.passive {
+      color: $passive-text-color;
+    }
+
+    &.active.passive {
+      color: $active-passive-text-color;
+    }
+  }
+
   @media screen and (max-width: $no-gap-breakpoint) {
     tbody td.optional {
       display: none;
diff --git a/app/models/account_stat.rb b/app/models/account_stat.rb
index c84e4217c..e70b54d79 100644
--- a/app/models/account_stat.rb
+++ b/app/models/account_stat.rb
@@ -21,26 +21,26 @@ class AccountStat < ApplicationRecord
 
   def increment_count!(key)
     update(attributes_for_increment(key))
-  rescue ActiveRecord::StaleObjectError
+  rescue ActiveRecord::StaleObjectError, ActiveRecord::RecordNotUnique
     begin
       reload_with_id
     rescue ActiveRecord::RecordNotFound
-      # Nothing to do
-    else
-      retry
+      return
     end
+
+    retry
   end
 
   def decrement_count!(key)
-    update(key => [public_send(key) - 1, 0].max)
-  rescue ActiveRecord::StaleObjectError
+    update(attributes_for_decrement(key))
+  rescue ActiveRecord::StaleObjectError, ActiveRecord::RecordNotUnique
     begin
       reload_with_id
     rescue ActiveRecord::RecordNotFound
-      # Nothing to do
-    else
-      retry
+      return
     end
+
+    retry
   end
 
   private
@@ -51,8 +51,13 @@ class AccountStat < ApplicationRecord
     attrs
   end
 
+  def attributes_for_decrement(key)
+    attrs = { key => [public_send(key) - 1, 0].max }
+    attrs
+  end
+
   def reload_with_id
-    self.id = find_by!(account: account).id if new_record?
+    self.id = self.class.find_by!(account: account).id if new_record?
     reload
   end
 end
diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb
index 7b9e40f68..882770d7c 100644
--- a/app/models/form/account_batch.rb
+++ b/app/models/form/account_batch.rb
@@ -9,6 +9,8 @@ class Form::AccountBatch
 
   def save
     case action
+    when 'follow'
+      follow!
     when 'unfollow'
       unfollow!
     when 'remove_from_followers'
@@ -24,6 +26,12 @@ class Form::AccountBatch
 
   private
 
+  def follow!
+    accounts.find_each do |target_account|
+      FollowService.new.call(current_account, target_account)
+    end
+  end
+
   def unfollow!
     accounts.find_each do |target_account|
       UnfollowService.new.call(current_account, target_account)
diff --git a/app/models/tag.rb b/app/models/tag.rb
index df2f86d95..bb93a52e2 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -126,7 +126,7 @@ class Tag < ApplicationRecord
     end
 
     def search_for(term, limit = 5, offset = 0, options = {})
-      normalized_term = normalize(term.strip).mb_chars.downcase.to_s
+      normalized_term = normalize(term.strip)
       pattern         = sanitize_sql_like(normalized_term) + '%'
       query           = Tag.listable.where(arel_table[:name].lower.matches(pattern))
       query           = query.where(arel_table[:name].lower.eq(normalized_term).or(arel_table[:reviewed_at].not_eq(nil))) if options[:exclude_unreviewed]
diff --git a/app/views/relationships/_account.html.haml b/app/views/relationships/_account.html.haml
index af5a4aaf7..f521aff22 100644
--- a/app/views/relationships/_account.html.haml
+++ b/app/views/relationships/_account.html.haml
@@ -5,6 +5,8 @@
     %table.accounts-table
       %tbody
         %tr
+          %td.accounts-table__interrelationships
+            = interrelationships_icon(@relationships, account.id)
           %td= account_link_to account
           %td.accounts-table__count.optional
             = number_to_human account.statuses_count, strip_insignificant_zeros: true
diff --git a/app/views/relationships/show.html.haml b/app/views/relationships/show.html.haml
index a6f8cdc15..7ad4e08f6 100644
--- a/app/views/relationships/show.html.haml
+++ b/app/views/relationships/show.html.haml
@@ -39,6 +39,8 @@
       %label.batch-table__toolbar__select.batch-checkbox-all
         = check_box_tag :batch_checkbox_all, nil, false
       .batch-table__toolbar__actions
+        = f.button safe_join([fa_icon('user-plus'), t('relationships.follow_selected_followers')]), name: :follow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship? && !mutual_relationship?
+
         = f.button safe_join([fa_icon('user-times'), t('relationships.remove_selected_follows')]), name: :unfollow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless followed_by_relationship?
 
         = f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless following_relationship?