about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/auth/registrations_controller.rb4
-rw-r--r--app/controllers/statuses_controller.rb7
-rw-r--r--app/helpers/settings_helper.rb1
-rw-r--r--app/javascript/mastodon/actions/notifications.js2
-rw-r--r--app/javascript/mastodon/actions/push_notifications/index.js4
-rw-r--r--app/javascript/mastodon/actions/push_notifications/setter.js4
-rw-r--r--app/javascript/mastodon/actions/settings.js6
-rw-r--r--app/javascript/mastodon/features/compose/containers/warning_container.js9
-rw-r--r--app/javascript/mastodon/features/notifications/components/column_settings.js4
-rw-r--r--app/javascript/mastodon/features/notifications/containers/column_settings_container.js8
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json259
-rw-r--r--app/javascript/mastodon/locales/sr.json18
-rw-r--r--app/javascript/mastodon/reducers/push_notifications.js2
-rw-r--r--app/javascript/mastodon/reducers/settings.js2
-rw-r--r--app/javascript/styles/mastodon/components.scss5
-rw-r--r--app/lib/activity_tracker.rb2
-rw-r--r--app/lib/activitypub/activity/delete.rb1
-rw-r--r--app/lib/sanitize_config.rb8
-rw-r--r--app/mailers/user_mailer.rb15
-rw-r--r--app/models/user.rb11
-rw-r--r--app/services/activitypub/process_account_service.rb2
-rwxr-xr-xapp/views/layouts/application.html.haml3
-rw-r--r--app/views/user_mailer/confirmation_instructions.sr-Latn.html.erb15
-rw-r--r--app/views/user_mailer/confirmation_instructions.sr-Latn.text.erb12
-rw-r--r--app/views/user_mailer/email_changed.en.html.erb15
-rw-r--r--app/views/user_mailer/email_changed.en.text.erb13
-rw-r--r--app/views/user_mailer/password_change.sr-Latn.html.erb3
-rw-r--r--app/views/user_mailer/password_change.sr-Latn.text.erb3
-rw-r--r--app/views/user_mailer/reconfirmation_instructions.en.html.erb15
-rw-r--r--app/views/user_mailer/reconfirmation_instructions.en.text.erb12
-rw-r--r--app/views/user_mailer/reset_password_instructions.sr-Latn.html.erb8
-rw-r--r--app/views/user_mailer/reset_password_instructions.sr-Latn.text.erb8
32 files changed, 442 insertions, 39 deletions
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index f4247fd95..2b6a1bdbc 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -38,6 +38,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
     new_user_session_path
   end
 
+  def after_update_path_for(_resource)
+    edit_user_registration_path
+  end
+
   def check_enabled_registrations
     redirect_to root_path if single_user_mode? || !allowed_registrations?
   end
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index 84c9e7685..6a635fba2 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -10,6 +10,7 @@ class StatusesController < ApplicationController
   before_action :set_link_headers
   before_action :check_account_suspension
   before_action :redirect_to_original, only: [:show]
+  before_action { response.headers['Vary'] = 'Accept' }
 
   def show
     respond_to do |format|
@@ -26,6 +27,12 @@ class StatusesController < ApplicationController
                serializer: ActivityPub::NoteSerializer,
                adapter: ActivityPub::Adapter,
                content_type: 'application/activity+json'
+
+        # Allow HTTP caching for 3 minutes if the status is public
+        unless @stream_entry.hidden?
+          request.session_options[:skip] = true
+          expires_in(3.minutes, public: true)
+        end
       end
     end
   end
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index ce8aadcc6..a63eb5e43 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -30,6 +30,7 @@ module SettingsHelper
     ru: 'Русский',
     sk: 'Slovensky',
     sr: 'Српски',
+    'sr-Latn': 'Srpski (latinica)',
     sv: 'Svenska',
     th: 'ภาษาไทย',
     tr: 'Türkçe',
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index b24ac8b73..502690045 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -31,7 +31,7 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
 
 const unescapeHTML = (html) => {
   const wrapper = document.createElement('div');
-  html = html.replace(/<br \/>|<br>|\n/, ' ');
+  html = html.replace(/<br \/>|<br>|\n/g, ' ');
   wrapper.innerHTML = html;
   return wrapper.textContent;
 };
diff --git a/app/javascript/mastodon/actions/push_notifications/index.js b/app/javascript/mastodon/actions/push_notifications/index.js
index 376b55b62..2ffec500a 100644
--- a/app/javascript/mastodon/actions/push_notifications/index.js
+++ b/app/javascript/mastodon/actions/push_notifications/index.js
@@ -15,9 +15,9 @@ export {
   register,
 };
 
-export function changeAlerts(key, value) {
+export function changeAlerts(path, value) {
   return dispatch => {
-    dispatch(setAlerts(key, value));
+    dispatch(setAlerts(path, value));
     dispatch(saveSettings());
   };
 }
diff --git a/app/javascript/mastodon/actions/push_notifications/setter.js b/app/javascript/mastodon/actions/push_notifications/setter.js
index a2cc41c5a..5561766bf 100644
--- a/app/javascript/mastodon/actions/push_notifications/setter.js
+++ b/app/javascript/mastodon/actions/push_notifications/setter.js
@@ -23,11 +23,11 @@ export function clearSubscription () {
   };
 }
 
-export function setAlerts (key, value) {
+export function setAlerts (path, value) {
   return dispatch => {
     dispatch({
       type: SET_ALERTS,
-      key,
+      path,
       value,
     });
   };
diff --git a/app/javascript/mastodon/actions/settings.js b/app/javascript/mastodon/actions/settings.js
index 79adca18c..aeef43527 100644
--- a/app/javascript/mastodon/actions/settings.js
+++ b/app/javascript/mastodon/actions/settings.js
@@ -4,11 +4,11 @@ import { debounce } from 'lodash';
 export const SETTING_CHANGE = 'SETTING_CHANGE';
 export const SETTING_SAVE   = 'SETTING_SAVE';
 
-export function changeSetting(key, value) {
+export function changeSetting(path, value) {
   return dispatch => {
     dispatch({
       type: SETTING_CHANGE,
-      key,
+      path,
       value,
     });
 
@@ -21,7 +21,7 @@ const debouncedSave = debounce((dispatch, getState) => {
     return;
   }
 
-  const data = getState().get('settings').filter((_, key) => key !== 'saved').toJS();
+  const data = getState().get('settings').filter((_, path) => path !== 'saved').toJS();
 
   axios.put('/api/web/settings', { data }).then(() => dispatch({ type: SETTING_SAVE }));
 }, 5000, { trailing: true });
diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.js b/app/javascript/mastodon/features/compose/containers/warning_container.js
index d34471a3e..b9f280958 100644
--- a/app/javascript/mastodon/features/compose/containers/warning_container.js
+++ b/app/javascript/mastodon/features/compose/containers/warning_container.js
@@ -5,20 +5,27 @@ import PropTypes from 'prop-types';
 import { FormattedMessage } from 'react-intl';
 import { me } from '../../../initial_state';
 
+const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i;
+
 const mapStateToProps = state => ({
   needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
+  hashtagWarning: state.getIn(['compose', 'privacy']) !== 'public' && APPROX_HASHTAG_RE.test(state.getIn(['compose', 'text'])),
 });
 
-const WarningWrapper = ({ needsLockWarning }) => {
+const WarningWrapper = ({ needsLockWarning, hashtagWarning }) => {
   if (needsLockWarning) {
     return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />;
   }
+  if (hashtagWarning) {
+    return <Warning message={<FormattedMessage id='compose_form.hashtag_warning' defaultMessage="This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag." />} />;
+  }
 
   return null;
 };
 
 WarningWrapper.propTypes = {
   needsLockWarning: PropTypes.bool,
+  hashtagWarning: PropTypes.bool,
 };
 
 export default connect(mapStateToProps)(WarningWrapper);
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js
index 23545185c..d9638aaf3 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.js
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.js
@@ -14,8 +14,8 @@ export default class ColumnSettings extends React.PureComponent {
     onClear: PropTypes.func.isRequired,
   };
 
-  onPushChange = (key, checked) => {
-    this.props.onChange(['push', ...key], checked);
+  onPushChange = (path, checked) => {
+    this.props.onChange(['push', ...path], checked);
   }
 
   render () {
diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
index f4c63fee6..e9cef0a7b 100644
--- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
+++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
@@ -18,11 +18,11 @@ const mapStateToProps = state => ({
 
 const mapDispatchToProps = (dispatch, { intl }) => ({
 
-  onChange (key, checked) {
-    if (key[0] === 'push') {
-      dispatch(changePushNotifications(key.slice(1), checked));
+  onChange (path, checked) {
+    if (path[0] === 'push') {
+      dispatch(changePushNotifications(path.slice(1), checked));
     } else {
-      dispatch(changeSetting(['notifications', ...key], checked));
+      dispatch(changeSetting(['notifications', ...path], checked));
     }
   },
 
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
new file mode 100644
index 000000000..971ffb5c5
--- /dev/null
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -0,0 +1,259 @@
+{
+  "account.block": "Blokiraj korisnika @{name}",
+  "account.block_domain": "Sakrij sve sa domena {domain}",
+  "account.disclaimer_full": "Navedene informacije možda ne odslikavaju korisnički profil u potpunosti.",
+  "account.edit_profile": "Izmeni profil",
+  "account.follow": "Zaprati",
+  "account.followers": "Pratioca",
+  "account.follows": "Prati",
+  "account.follows_you": "Prati Vas",
+  "account.hide_reblogs": "Sakrij podrške koje daje korisnika @{name}",
+  "account.media": "Mediji",
+  "account.mention": "Pomeni korisnika @{name}",
+  "account.moved_to": "{name} se pomerio na:",
+  "account.mute": "Mutiraj @{name}",
+  "account.mute_notifications": "Isključi obaveštenja od korisnika @{name}",
+  "account.posts": "Statusa",
+  "account.report": "Prijavi @{name}",
+  "account.requested": "Čekam odobrenje. Kliknite da poništite zahtev za praćenje",
+  "account.share": "Podeli profil korisnika @{name}",
+  "account.show_reblogs": "Prikaži podrške od korisnika @{name}",
+  "account.unblock": "Odblokiraj korisnika @{name}",
+  "account.unblock_domain": "Odblokiraj domen {domain}",
+  "account.unfollow": "Otprati",
+  "account.unmute": "Odmutiraj @{name}",
+  "account.unmute_notifications": "Uključi nazad obaveštenja od korisnika @{name}",
+  "account.view_full_profile": "Vidi ceo profil",
+  "boost_modal.combo": "Možete pritisnuti {combo} da preskočite ovo sledeći put",
+  "bundle_column_error.body": "Nešto je pošlo po zlu prilikom učitavanja ove komponente.",
+  "bundle_column_error.retry": "Pokušajte ponovo",
+  "bundle_column_error.title": "Mrežna greška",
+  "bundle_modal_error.close": "Zatvori",
+  "bundle_modal_error.message": "Nešto nije bilo u redu pri učitavanju ove komponente.",
+  "bundle_modal_error.retry": "Pokušajte ponovo",
+  "column.blocks": "Blokirani korisnici",
+  "column.community": "Lokalna lajna",
+  "column.favourites": "Omiljeni",
+  "column.follow_requests": "Zahtevi za praćenje",
+  "column.home": "Početna",
+  "column.lists": "Liste",
+  "column.mutes": "Mutirani korisnici",
+  "column.notifications": "Obaveštenja",
+  "column.pins": "Prikačeni tutovi",
+  "column.public": "Združena lajna",
+  "column_back_button.label": "Nazad",
+  "column_header.hide_settings": "Sakrij postavke",
+  "column_header.moveLeft_settings": "Pomeri kolonu ulevo",
+  "column_header.moveRight_settings": "Pomeri kolonu udesno",
+  "column_header.pin": "Prikači",
+  "column_header.show_settings": "Prikaži postavke",
+  "column_header.unpin": "Otkači",
+  "column_subheading.navigation": "Navigacija",
+  "column_subheading.settings": "Postavke",
+  "compose_form.lock_disclaimer": "Vaš nalog nije {locked}. Svako može da Vas zaprati i da vidi objave namenjene samo Vašim pratiocima.",
+  "compose_form.lock_disclaimer.lock": "zaključan",
+  "compose_form.placeholder": "Šta Vam je na umu?",
+  "compose_form.publish": "Tutni",
+  "compose_form.publish_loud": "{publish}!",
+  "compose_form.sensitive": "Obeleži multimediju kao osetljivu",
+  "compose_form.spoiler": "Sakrij tekst ispod upozorenja",
+  "compose_form.spoiler_placeholder": "Ovde upišite upozorenje",
+  "confirmation_modal.cancel": "Poništi",
+  "confirmations.block.confirm": "Blokiraj",
+  "confirmations.block.message": "Da li ste sigurni da želite da blokirate korisnika {name}?",
+  "confirmations.delete.confirm": "Obriši",
+  "confirmations.delete.message": "Da li ste sigurni da želite obrišete ovaj status?",
+  "confirmations.delete_list.confirm": "Obriši",
+  "confirmations.delete_list.message": "Da li ste sigurni da želite da bespovratno obrišete ovu listu?",
+  "confirmations.domain_block.confirm": "Sakrij ceo domen",
+  "confirmations.domain_block.message": "Da li ste stvarno, stvarno sigurno da želite da blokirate ceo domen {domain}? U većini slučajeva, par dobrih blokiranja ili mutiranja su dovoljna i preporučljiva.",
+  "confirmations.mute.confirm": "Mutiraj",
+  "confirmations.mute.message": "Da li stvarno želite da mutirate korisnika {name}?",
+  "confirmations.unfollow.confirm": "Otprati",
+  "confirmations.unfollow.message": "Da li ste sigurni da želite da otpratite korisnika {name}?",
+  "embed.instructions": "Ugradi ovaj status na Vaš veb sajt kopiranjem koda ispod.",
+  "embed.preview": "Ovako će da izgleda:",
+  "emoji_button.activity": "Aktivnost",
+  "emoji_button.custom": "Proizvoljno",
+  "emoji_button.flags": "Zastave",
+  "emoji_button.food": "Hrana & piće",
+  "emoji_button.label": "Ubaci smajli",
+  "emoji_button.nature": "Priroda",
+  "emoji_button.not_found": "Nema smajlija!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.objects": "Objekti",
+  "emoji_button.people": "Ljudi",
+  "emoji_button.recent": "Najčešće korišćeni",
+  "emoji_button.search": "Pretraga...",
+  "emoji_button.search_results": "Rezultati pretrage",
+  "emoji_button.symbols": "Simboli",
+  "emoji_button.travel": "Putovanja & mesta",
+  "empty_column.community": "Lokalna lajna je prazna. Napišite nešto javno da lajna produva!",
+  "empty_column.hashtag": "Trenutno nema ništa na ovom heštegu.",
+  "empty_column.home": "Vaša lajna je prazna! Posetite {public} ili koristite pretragu da počnete i upoznajete nove ljude.",
+  "empty_column.home.public_timeline": "javna lajna",
+  "empty_column.list": "U ovoj listi još nema ničega. Kada članovi liste objave nove statuse, oni će se pojavljivati ovde.",
+  "empty_column.notifications": "Trenutno nemate obaveštenja. Družite se malo da započnete razgovore.",
+  "empty_column.public": "Ovde nema ničega! Napišite nešto javno, ili nađite korisnike sa drugih instanci koje ćete zapratiti da popunite ovu prazninu",
+  "follow_request.authorize": "Odobri",
+  "follow_request.reject": "Odbij",
+  "getting_started.appsshort": "Aplikacije",
+  "getting_started.faq": "ČPP",
+  "getting_started.heading": "Da počnete",
+  "getting_started.open_source_notice": "Mastodont je softver otvorenog koda. Možete mu doprineti ili prijaviti probleme preko GitHub-a na {github}.",
+  "getting_started.userguide": "Korisničko uputstvo",
+  "home.column_settings.advanced": "Napredno",
+  "home.column_settings.basic": "Osnovno",
+  "home.column_settings.filter_regex": "Filtriraj regularnim izrazima",
+  "home.column_settings.show_reblogs": "Prikaži i podržavanja",
+  "home.column_settings.show_replies": "Prikaži odgovore",
+  "home.settings": "Postavke kolone",
+  "keyboard_shortcuts.back": "da odete nazad",
+  "keyboard_shortcuts.boost": "da podržite",
+  "keyboard_shortcuts.column": "da se prebacite na status u jednoj od kolona",
+  "keyboard_shortcuts.compose": "da se prebacite na pisanje novog tuta",
+  "keyboard_shortcuts.description": "Opis",
+  "keyboard_shortcuts.down": "da se pomerite na dole u listi",
+  "keyboard_shortcuts.enter": "da otvorite status",
+  "keyboard_shortcuts.favourite": "da označite kao omiljeno",
+  "keyboard_shortcuts.heading": "Prečice na tastaturi",
+  "keyboard_shortcuts.hotkey": "Prečica",
+  "keyboard_shortcuts.legend": "da prikažete ovaj podsetnik",
+  "keyboard_shortcuts.mention": "da pomenete autora",
+  "keyboard_shortcuts.reply": "da odgovorite",
+  "keyboard_shortcuts.search": "da se prebacite na pretragu",
+  "keyboard_shortcuts.toot": "da započnete skroz novi tut",
+  "keyboard_shortcuts.unfocus": "da ne budete više na pretrazi/pravljenju novog tuta",
+  "keyboard_shortcuts.up": "da se pomerite na gore u listi",
+  "lightbox.close": "Zatvori",
+  "lightbox.next": "Sledeći",
+  "lightbox.previous": "Prethodni",
+  "lists.account.add": "Dodaj na listu",
+  "lists.account.remove": "Ukloni sa liste",
+  "lists.delete": "Obriši listu",
+  "lists.edit": "Izmeni listu",
+  "lists.new.create": "Dodaj listu",
+  "lists.new.title_placeholder": "Naslov nove liste",
+  "lists.search": "Pretraži među ljudima koje pratite",
+  "lists.subheading": "Vaše liste",
+  "loading_indicator.label": "Učitavam...",
+  "media_gallery.toggle_visible": "Uključi/isključi vidljivost",
+  "missing_indicator.label": "Nije pronađeno",
+  "mute_modal.hide_notifications": "Sakrij obaveštenja od ovog korisnika?",
+  "navigation_bar.blocks": "Blokirani korisnici",
+  "navigation_bar.community_timeline": "Lokalna lajna",
+  "navigation_bar.edit_profile": "Izmeni profil",
+  "navigation_bar.favourites": "Omiljeni",
+  "navigation_bar.follow_requests": "Zahtevi za praćenje",
+  "navigation_bar.info": "O ovoj instanci",
+  "navigation_bar.keyboard_shortcuts": "Prečice na tastaturi",
+  "navigation_bar.lists": "Liste",
+  "navigation_bar.logout": "Odjava",
+  "navigation_bar.mutes": "Mutirani korisnici",
+  "navigation_bar.pins": "Prikačeni tutovi",
+  "navigation_bar.preferences": "Podešavanja",
+  "navigation_bar.public_timeline": "Združena lajna",
+  "notification.favourite": "{name} je stavio Vaš status kao omiljeni",
+  "notification.follow": "{name} Vas je zapratio",
+  "notification.mention": "{name} Vas je pomenuo",
+  "notification.reblog": "{name} je podržao(la) Vaš status",
+  "notifications.clear": "Očisti obaveštenja",
+  "notifications.clear_confirmation": "Da li ste sigurno da trajno želite da očistite Vaša obaveštenja?",
+  "notifications.column_settings.alert": "Obaveštenja na radnoj površini",
+  "notifications.column_settings.favourite": "Omiljeni:",
+  "notifications.column_settings.follow": "Novi pratioci:",
+  "notifications.column_settings.mention": "Pominjanja:",
+  "notifications.column_settings.push": "Guraj obaveštenja",
+  "notifications.column_settings.push_meta": "Ovaj uređaj",
+  "notifications.column_settings.reblog": "Podrški:",
+  "notifications.column_settings.show": "Prikaži u koloni",
+  "notifications.column_settings.sound": "Puštaj zvuk",
+  "onboarding.done": "Gotovo",
+  "onboarding.next": "Sledeće",
+  "onboarding.page_five.public_timelines": "Lokalna lajna prikazuje sve javne statuse od svih na domenu {domain}. Združena lajna prikazuje javne statuse od svih ljudi koje prate korisnici sa domena {domain}. Ovo su javne lajne, sjajan način da otkrijete nove ljude.",
+  "onboarding.page_four.home": "Početna lajna prikazuje statuse ljudi koje Vi pratite.",
+  "onboarding.page_four.notifications": "Kolona sa obaveštenjima Vam prikazuje kada neko priča sa Vama.",
+  "onboarding.page_one.federation": "Mastodont je mreža nezavisnih servera koji se uvezuju da naprave jednu veću društvenu mrežu. Ove servere zovemo instancama.",
+  "onboarding.page_one.handle": "Vi ste na domenu {domain}, pa je Vaša puna identifikacija {handle}",
+  "onboarding.page_one.welcome": "Dobrodošli na Mastodont!",
+  "onboarding.page_six.admin": "Administrator Vaše instance je {admin}.",
+  "onboarding.page_six.almost_done": "Još malo, pa gotovo...",
+  "onboarding.page_six.appetoot": "Prijatutno!",
+  "onboarding.page_six.apps_available": "Postoje {apps} dostupne za iOS, Android i druge platforme.",
+  "onboarding.page_six.github": "Mastodont je slobodan softver otvorenog koda. Možete prijavljivati greške, potraživati nove funckionalnosti, ili učestvujući u programiranju. Naš izvorni kod je ovde: {github}.",
+  "onboarding.page_six.guidelines": "smernice zajednice",
+  "onboarding.page_six.read_guidelines": "Pročitejte {guidelines} domena {domain}!",
+  "onboarding.page_six.various_app": "mobilne aplikacije",
+  "onboarding.page_three.profile": "Izmenite profil da promenite avatar, biografiju i ime za prikaz. Tamo ćete naći i ostala podešavanja.",
+  "onboarding.page_three.search": "Korisite pretragu da nađete ljude i gledate heštegove, kao što su {illustration} i {introductions}. Da nađete osobu koja nije na ovoj instanci, koristite njenu punu identifikaciju.",
+  "onboarding.page_two.compose": "Pišite statuse iz prve kolone. Možete otpremati slike, menjati podešavanja privatnosti, i dodavati upozorenja za osetljiv sadržaj preko ikonica ispod.",
+  "onboarding.skip": "Preskoči",
+  "privacy.change": "Podesi status privatnosti",
+  "privacy.direct.long": "Objavi samo korisnicima koji su pomenuti",
+  "privacy.direct.short": "Direktno",
+  "privacy.private.long": "Objavi samo pratiocima",
+  "privacy.private.short": "Samo za pratioce",
+  "privacy.public.long": "Objavi na javnoj lajni",
+  "privacy.public.short": "Javno",
+  "privacy.unlisted.long": "Ne objavljuj na javnim lajnama",
+  "privacy.unlisted.short": "Neizlistano",
+  "relative_time.days": "{number}d",
+  "relative_time.hours": "{number}h",
+  "relative_time.just_now": "sada",
+  "relative_time.minutes": "{number}m",
+  "relative_time.seconds": "{number}s",
+  "reply_indicator.cancel": "Poništi",
+  "report.placeholder": "Dodatni komentari",
+  "report.submit": "Pošalji",
+  "report.target": "Prijavljujem {target}",
+  "search.placeholder": "Pretraga",
+  "search_popout.search_format": "Napredni format pretrage",
+  "search_popout.tips.hashtag": "hešteg",
+  "search_popout.tips.status": "status",
+  "search_popout.tips.text": "Traženjem običnog teksta ćete dobiti sva pronađena imena, sva korisnička imena i sve nađene heštegove",
+  "search_popout.tips.user": "korisnik",
+  "search_results.total": "{count, number} {count, plural, one {rezultat} few {rezultata} other {rezultata}}",
+  "standalone.public_title": "Pogled iznutra...",
+  "status.cannot_reblog": "Ovaj status ne može da se podrži",
+  "status.delete": "Obriši",
+  "status.embed": "Ugradi na sajt",
+  "status.favourite": "Omiljeno",
+  "status.load_more": "Učitaj još",
+  "status.media_hidden": "Multimedija sakrivena",
+  "status.mention": "Pomeni korisnika @{name}",
+  "status.more": "Još",
+  "status.mute_conversation": "Mutiraj prepisku",
+  "status.open": "Proširi ovaj status",
+  "status.pin": "Prikači na profil",
+  "status.reblog": "Podrži",
+  "status.reblogged_by": "{name} podržao(la)",
+  "status.reply": "Odgovori",
+  "status.replyAll": "Odgovori na diskusiju",
+  "status.report": "Prijavi korisnika @{name}",
+  "status.sensitive_toggle": "Kliknite da vidite",
+  "status.sensitive_warning": "Osetljiv sadržaj",
+  "status.share": "Podeli",
+  "status.show_less": "Prikaži manje",
+  "status.show_more": "Prikaži više",
+  "status.unmute_conversation": "Uključi prepisku",
+  "status.unpin": "Otkači sa profila",
+  "tabs_bar.compose": "Napiši",
+  "tabs_bar.federated_timeline": "Združeno",
+  "tabs_bar.home": "Početna",
+  "tabs_bar.local_timeline": "Lokalno",
+  "tabs_bar.notifications": "Obaveštenja",
+  "ui.beforeunload": "Ako napustite Mastodont, izgubićete napisani nacrt.",
+  "upload_area.title": "Prevucite ovde da otpremite",
+  "upload_button.label": "Dodaj multimediju",
+  "upload_form.description": "Opiši za slabovide osobe",
+  "upload_form.undo": "Opozovi",
+  "upload_progress.label": "Otpremam...",
+  "video.close": "Zatvori video",
+  "video.exit_fullscreen": "Napusti ceo ekran",
+  "video.expand": "Proširi video",
+  "video.fullscreen": "Ceo ekran",
+  "video.hide": "Sakrij video",
+  "video.mute": "Ugasi zvuk",
+  "video.pause": "Pauziraj",
+  "video.play": "Pusti",
+  "video.unmute": "Vrati zvuk"
+}
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index f56d553b9..c8e4573e6 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -7,7 +7,7 @@
   "account.followers": "Пратиоца",
   "account.follows": "Прати",
   "account.follows_you": "Прати Вас",
-  "account.hide_reblogs": "Сакриј погуравања од корисника @{name}",
+  "account.hide_reblogs": "Сакриј подршке које даје корисника @{name}",
   "account.media": "Медији",
   "account.mention": "Помени корисника @{name}",
   "account.moved_to": "{name} се померио на:",
@@ -17,7 +17,7 @@
   "account.report": "Пријави @{name}",
   "account.requested": "Чекам одобрење. Кликните да поништите захтев за праћење",
   "account.share": "Подели профил корисника @{name}",
-  "account.show_reblogs": "Прикажи погуравања од корисника @{name}",
+  "account.show_reblogs": "Прикажи подршке од корисника @{name}",
   "account.unblock": "Одблокирај корисника @{name}",
   "account.unblock_domain": "Одблокирај домен {domain}",
   "account.unfollow": "Отпрати",
@@ -104,11 +104,11 @@
   "home.column_settings.advanced": "Напредно",
   "home.column_settings.basic": "Основно",
   "home.column_settings.filter_regex": "Филтрирај регуларним изразима",
-  "home.column_settings.show_reblogs": "Прикажи и погуравања",
+  "home.column_settings.show_reblogs": "Прикажи и подржавања",
   "home.column_settings.show_replies": "Прикажи одговоре",
   "home.settings": "Поставке колоне",
   "keyboard_shortcuts.back": "да одете назад",
-  "keyboard_shortcuts.boost": "да погурате",
+  "keyboard_shortcuts.boost": "да подржите",
   "keyboard_shortcuts.column": "да се пребаците на статус у једној од колона",
   "keyboard_shortcuts.compose": "да се пребаците на писање новог тута",
   "keyboard_shortcuts.description": "Опис",
@@ -155,7 +155,7 @@
   "notification.favourite": "{name} је ставио Ваш статус као омиљени",
   "notification.follow": "{name} Вас је запратио",
   "notification.mention": "{name} Вас је поменуо",
-  "notification.reblog": "{name} је погурао Ваш статус",
+  "notification.reblog": "{name} је подржао(ла) Ваш статус",
   "notifications.clear": "Очисти обавештења",
   "notifications.clear_confirmation": "Да ли сте сигурно да трајно желите да очистите Ваша обавештења?",
   "notifications.column_settings.alert": "Обавештења на радној површини",
@@ -164,7 +164,7 @@
   "notifications.column_settings.mention": "Помињања:",
   "notifications.column_settings.push": "Гурај обавештења",
   "notifications.column_settings.push_meta": "Овај уређај",
-  "notifications.column_settings.reblog": "Погуравања:",
+  "notifications.column_settings.reblog": "Подршки:",
   "notifications.column_settings.show": "Прикажи у колони",
   "notifications.column_settings.sound": "Пуштај звук",
   "onboarding.done": "Готово",
@@ -213,7 +213,7 @@
   "search_popout.tips.user": "корисник",
   "search_results.total": "{count, number} {count, plural, one {резултат} few {резултата} other {резултата}}",
   "standalone.public_title": "Поглед изнутра...",
-  "status.cannot_reblog": "Овај статус не може да се погура",
+  "status.cannot_reblog": "Овај статус не може да се подржи",
   "status.delete": "Обриши",
   "status.embed": "Угради на сајт",
   "status.favourite": "Омиљено",
@@ -224,8 +224,8 @@
   "status.mute_conversation": "Мутирај преписку",
   "status.open": "Прошири овај статус",
   "status.pin": "Прикачи на профил",
-  "status.reblog": "Погурај",
-  "status.reblogged_by": "{name} погурао(ла)",
+  "status.reblog": "Подржи",
+  "status.reblogged_by": "{name} подржао(ла)",
   "status.reply": "Одговори",
   "status.replyAll": "Одговори на дискусију",
   "status.report": "Пријави корисника @{name}",
diff --git a/app/javascript/mastodon/reducers/push_notifications.js b/app/javascript/mastodon/reducers/push_notifications.js
index c15b38fe4..85628c6b1 100644
--- a/app/javascript/mastodon/reducers/push_notifications.js
+++ b/app/javascript/mastodon/reducers/push_notifications.js
@@ -44,7 +44,7 @@ export default function push_subscriptions(state = initialState, action) {
   case CLEAR_SUBSCRIPTION:
     return initialState;
   case SET_ALERTS:
-    return state.setIn(action.key, action.value);
+    return state.setIn(action.path, action.value);
   default:
     return state;
   }
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
index 5817cf49b..390b2a13a 100644
--- a/app/javascript/mastodon/reducers/settings.js
+++ b/app/javascript/mastodon/reducers/settings.js
@@ -93,7 +93,7 @@ export default function settings(state = initialState, action) {
     return hydrate(state, action.state.get('settings'));
   case SETTING_CHANGE:
     return state
-      .setIn(action.key, action.value)
+      .setIn(action.path, action.value)
       .set('saved', false);
   case COLUMN_ADD:
     return state
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index a03165690..a09a766d0 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -2072,6 +2072,11 @@
   cursor: default;
 }
 
+.getting-started__wrapper,
+.getting_started {
+  background: $ui-base-color;
+}
+
 .getting-started__wrapper {
   position: relative;
   overflow-y: auto;
diff --git a/app/lib/activity_tracker.rb b/app/lib/activity_tracker.rb
index 50e927b0c..5b4972674 100644
--- a/app/lib/activity_tracker.rb
+++ b/app/lib/activity_tracker.rb
@@ -15,7 +15,7 @@ class ActivityTracker
       key = [prefix, current_week].join(':')
 
       redis.pfadd(key, value)
-      redis.expire(key, value)
+      redis.expire(key, EXPIRE_AFTER)
     end
 
     private
diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb
index d0fb49342..5fa60a81c 100644
--- a/app/lib/activitypub/activity/delete.rb
+++ b/app/lib/activitypub/activity/delete.rb
@@ -13,6 +13,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
 
   def delete_person
     SuspendAccountService.new.call(@account)
+    @account.destroy!
   end
 
   def delete_note
diff --git a/app/lib/sanitize_config.rb b/app/lib/sanitize_config.rb
index f09288fcd..c2b466924 100644
--- a/app/lib/sanitize_config.rb
+++ b/app/lib/sanitize_config.rb
@@ -6,14 +6,14 @@ class Sanitize
 
     CLASS_WHITELIST_TRANSFORMER = lambda do |env|
       node = env[:node]
-      class_list = node['class']&.split(' ')
+      class_list = node['class']&.split(/[\t\n\f\r ]/)
 
       return unless class_list
 
       class_list.keep_if do |e|
-        return true if e =~ /^(h|p|u|dt|e)-/ # microformats classes
-        return true if e =~ /^(mention|hashtag)$/ # semantic classes
-        return true if e =~ /^(ellipsis|invisible)$/ # link formatting classes
+        next true if e =~ /^(h|p|u|dt|e)-/ # microformats classes
+        next true if e =~ /^(mention|hashtag)$/ # semantic classes
+        next true if e =~ /^(ellipsis|invisible)$/ # link formatting classes
       end
 
       node['class'] = class_list.join(' ')
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 5a062dc25..7821be32b 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -13,7 +13,9 @@ class UserMailer < Devise::Mailer
     return if @resource.disabled?
 
     I18n.with_locale(@resource.locale || I18n.default_locale) do
-      mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email, subject: I18n.t('devise.mailer.confirmation_instructions.subject', instance: @instance)
+      mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email,
+           subject: I18n.t(@resource.pending_reconfirmation? ? 'devise.mailer.reconfirmation_instructions.subject' : 'devise.mailer.confirmation_instructions.subject', instance: @instance),
+           template_name: @resource.pending_reconfirmation? ? 'reconfirmation_instructions' : 'confirmation_instructions'
     end
   end
 
@@ -39,4 +41,15 @@ class UserMailer < Devise::Mailer
       mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject')
     end
   end
+
+  def email_changed(user, **)
+    @resource = user
+    @instance = Rails.configuration.x.local_domain
+
+    return if @resource.disabled?
+
+    I18n.with_locale(@resource.locale || I18n.default_locale) do
+      mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject')
+    end
+  end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index fed9c4977..231271f73 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -41,12 +41,15 @@ class User < ApplicationRecord
 
   ACTIVE_DURATION = 14.days
 
-  devise :registerable, :recoverable,
-         :rememberable, :trackable, :validatable, :confirmable,
-         :two_factor_authenticatable, :two_factor_backupable,
-         otp_secret_encryption_key: ENV['OTP_SECRET'],
+  devise :two_factor_authenticatable,
+         otp_secret_encryption_key: ENV['OTP_SECRET']
+
+  devise :two_factor_backupable,
          otp_number_of_backup_codes: 10
 
+  devise :registerable, :recoverable, :rememberable, :trackable, :validatable,
+         :confirmable
+
   belongs_to :account, inverse_of: :user, required: true
   belongs_to :invite, counter_cache: :uses
   accepts_nested_attributes_for :account
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 06ca75563..0fbf18c00 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -74,7 +74,7 @@ class ActivityPub::ProcessAccountService < BaseService
     @account.statuses_count    = outbox_total_items    if outbox_total_items.present?
     @account.following_count   = following_total_items if following_total_items.present?
     @account.followers_count   = followers_total_items if followers_total_items.present?
-    @account.moved_to_account  = moved_account         if @json['movedTo'].present?
+    @account.moved_to_account  = @json['movedTo'].present? ? moved_account : nil
   end
 
   def after_protocol_change!
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 5b9e652cb..322d7403e 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -2,8 +2,7 @@
 %html{ lang: I18n.locale }
   %head
     %meta{ charset: 'utf-8' }/
-    %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }/
-    %meta{ 'http-equiv' => 'X-UA-Compatible', content: 'IE=edge' }/
+    %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }/   
     %link{ rel: 'icon', href: favicon_path, type: 'image/x-icon' }/
     %link{ rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }/
     %link{ rel: 'mask-icon', href: '/mask-icon.svg', color: '#2B90D9' }/
diff --git a/app/views/user_mailer/confirmation_instructions.sr-Latn.html.erb b/app/views/user_mailer/confirmation_instructions.sr-Latn.html.erb
new file mode 100644
index 000000000..a16008250
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.sr-Latn.html.erb
@@ -0,0 +1,15 @@
+<p>Dobrodošao <%= @resource.email %> !</p>
+
+<p>Upravo ste napravili nalog na instanci <%= @instance %>.</p>
+
+<p>Da potvrdite Vašu registraciju, molimo Vas kliknite na sledeći link: <br>
+<%= link_to 'Potvrdi moj nalog', confirmation_url(@resource, confirmation_token: @token) %></p>
+
+<p>Ako link iznad ne radi, kopirajte i nalepite ovu adresu u adresnu traku: <br>
+<span><%= confirmation_url(@resource, confirmation_token: @token) %></span>
+
+<p>Takođe pogledajte i <%= link_to 'pravila i uslove korišćenja', terms_url %>.</p>
+
+<p>S poštovanjem,<p>
+
+<p><%= @instance %> tim</p>
diff --git a/app/views/user_mailer/confirmation_instructions.sr-Latn.text.erb b/app/views/user_mailer/confirmation_instructions.sr-Latn.text.erb
new file mode 100644
index 000000000..60fe9db0d
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.sr-Latn.text.erb
@@ -0,0 +1,12 @@
+Dobrodošao <%= @resource.email %> !
+
+Upravo ste napravili nalog na instanci <%= @instance %>.
+
+Da potvrdite Vašu registraciju, molimo Vas kliknite na sledeći link:
+<%= confirmation_url(@resource, confirmation_token: @token) %>
+
+Takođe pogledajte i pravila i uslove korišćenja <%= terms_url %>
+
+S poštovanjem,
+
+<%= @instance %> tim
diff --git a/app/views/user_mailer/email_changed.en.html.erb b/app/views/user_mailer/email_changed.en.html.erb
new file mode 100644
index 000000000..c10680086
--- /dev/null
+++ b/app/views/user_mailer/email_changed.en.html.erb
@@ -0,0 +1,15 @@
+<p>Hello <%= @resource.email %>!</p>
+
+<% if @resource&.unconfirmed_email? %>
+  <p>We're contacting you to notify you that the email you use on <%= @instance %> is being changed to <%= @resource.unconfirmed_email %>.</p>
+<% else %>
+  <p>We're contacting you to notify you that the email you use on <%= @instance %> has been changed to <%= @resource.email %>.</p>
+<% end %>
+
+<p>
+  If you did not change your email, it is likely that someone has gained access to your account. Please change your password immediately or contact the instance admin if you're locked out of your account.
+</p>
+
+<p>Sincerely,<p>
+
+<p>The <%= @instance %> team</p>
diff --git a/app/views/user_mailer/email_changed.en.text.erb b/app/views/user_mailer/email_changed.en.text.erb
new file mode 100644
index 000000000..971972461
--- /dev/null
+++ b/app/views/user_mailer/email_changed.en.text.erb
@@ -0,0 +1,13 @@
+Hello <%= @resource.email %>!
+
+<% if @resource&.unconfirmed_email? %>
+We're contacting you to notify you that the email you use on <%= @instance %> is being changed to <%= @resource.unconfirmed_email %>.
+<% else %>
+We're contacting you to notify you that the email you use on <%= @instance %> has been changed to <%= @resource.email %>.
+<% end %>
+
+If you did not change your email, it is likely that someone has gained access to your account. Please change your password immediately or contact the instance admin if you're locked out of your account.
+
+Sincerely,
+
+The <%= @instance %> team
diff --git a/app/views/user_mailer/password_change.sr-Latn.html.erb b/app/views/user_mailer/password_change.sr-Latn.html.erb
new file mode 100644
index 000000000..ab4e23bdf
--- /dev/null
+++ b/app/views/user_mailer/password_change.sr-Latn.html.erb
@@ -0,0 +1,3 @@
+<p>Zdravo <%= @resource.email %>!</p>
+
+<p>Želimo samo da Vas obavestimo da je Vaša lozinka na Mastodont instanci <%= @instance %> promenjena.</p>
diff --git a/app/views/user_mailer/password_change.sr-Latn.text.erb b/app/views/user_mailer/password_change.sr-Latn.text.erb
new file mode 100644
index 000000000..6e0666d8d
--- /dev/null
+++ b/app/views/user_mailer/password_change.sr-Latn.text.erb
@@ -0,0 +1,3 @@
+Zdravo <%= @resource.email %>!
+
+Želimo samo da Vas obavestimo da je Vaša lozinka na Mastodont instanci <%= @instance %> promenjena.
diff --git a/app/views/user_mailer/reconfirmation_instructions.en.html.erb b/app/views/user_mailer/reconfirmation_instructions.en.html.erb
new file mode 100644
index 000000000..31866a3c8
--- /dev/null
+++ b/app/views/user_mailer/reconfirmation_instructions.en.html.erb
@@ -0,0 +1,15 @@
+<p>Hello <%= @resource.unconfirmed_email %>!</p>
+
+<p>You requested a change to the email address you use on <%= @instance %>.</p>
+
+<p>To confirm your new email, please click on the following link:<br>
+<%= link_to 'Confirm my email address', confirmation_url(@resource, confirmation_token: @token) %></p>
+
+<p>If the above link did not work, copy and paste this URL into your address bar: <br>
+<span><%= confirmation_url(@resource, confirmation_token: @token) %></span>
+
+<p>Please also check out our <%= link_to 'terms and conditions', terms_url %>.</p>
+
+<p>Sincerely,<p>
+
+<p>The <%= @instance %> team</p>
diff --git a/app/views/user_mailer/reconfirmation_instructions.en.text.erb b/app/views/user_mailer/reconfirmation_instructions.en.text.erb
new file mode 100644
index 000000000..c1c735b3a
--- /dev/null
+++ b/app/views/user_mailer/reconfirmation_instructions.en.text.erb
@@ -0,0 +1,12 @@
+Hello <%= @resource.unconfirmed_email %>!
+
+You requested a change to the email address you use on <%= @instance %>.
+
+To confirm your new email, please click on the following link:
+<%= confirmation_url(@resource, confirmation_token: @token) %>
+
+Please also check out our terms and conditions <%= terms_url %>
+
+Sincerely,
+
+The <%= @instance %> team
diff --git a/app/views/user_mailer/reset_password_instructions.sr-Latn.html.erb b/app/views/user_mailer/reset_password_instructions.sr-Latn.html.erb
new file mode 100644
index 000000000..7dede16b2
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.sr-Latn.html.erb
@@ -0,0 +1,8 @@
+<p>Zdravo <%= @resource.email %>!</p>
+
+<p>Neko je zatražio link za promenu lozinke na instanci <%= @instance %>. Ovo možete uraditi klikom na link ispod.</p>
+
+<p><%= link_to 'Promeni moju lozinku', edit_password_url(@resource, reset_password_token: @token) %></p>
+
+<p>Ignorišite ovu poruku, ako niste Vi bili ti koji ste zatražili promenu lozinke.</p>
+<p>Lozinka se neće promeniti sve dok ne kliknete link iznad i ne napravite novu lozinku.</p>
diff --git a/app/views/user_mailer/reset_password_instructions.sr-Latn.text.erb b/app/views/user_mailer/reset_password_instructions.sr-Latn.text.erb
new file mode 100644
index 000000000..31707dee1
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.sr-Latn.text.erb
@@ -0,0 +1,8 @@
+Zdravo <%= @resource.email %>!
+
+Neko je zatražio link za promenu lozinke na instanci <%= @instance %>. Ovo možete uraditi preko linka ispod.
+
+<%= edit_password_url(@resource, reset_password_token: @token) %>
+
+Ignorišite ovu poruku, ako niste Vi bili ti koji ste zatražili promenu lozinke.
+Lozinka se neće promeniti sve dok ne kliknete link iznad i ne napravite novu lozinku.