From a5e861cd019eb7770b8524ecdb6237f33fd18d12 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sat, 26 Mar 2022 12:21:34 -0400
Subject: Set userLanguage cookie when setting interface language

---
 src/components/settings_modal/tabs/general_tab.vue | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

(limited to 'src/components/settings_modal/tabs/general_tab.vue')

diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index a2c6bffa..5bc0f516 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -4,7 +4,10 @@
       <h2>{{ $t('settings.interface') }}</h2>
       <ul class="setting-list">
         <li>
-          <interface-language-switcher />
+          <interface-language-switcher
+            :language="language"
+            :set-language="val => language = val"
+          />
         </li>
         <li v-if="instanceSpecificPanelPresent">
           <BooleanSetting path="hideISP">
-- 
cgit v1.2.3-70-g09d2


From d39de3022108c4a42d20fe2a38265179ae0062f9 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Sat, 26 Mar 2022 12:39:26 -0400
Subject: Add config opts for email language

---
 .../interface_language_switcher.vue                | 16 +++++++--
 src/components/settings_modal/tabs/general_tab.vue |  1 +
 src/components/settings_modal/tabs/profile_tab.js  | 38 ++++++++++++++--------
 src/components/settings_modal/tabs/profile_tab.vue |  7 ++++
 src/services/api/api.service.js                    |  1 +
 5 files changed, 46 insertions(+), 17 deletions(-)

(limited to 'src/components/settings_modal/tabs/general_tab.vue')

diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index 757ec01f..078535dc 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -1,7 +1,7 @@
 <template>
   <div>
     <label for="interface-language-switcher">
-      {{ $t('settings.interfaceLanguage') }}
+      {{ promptText }}
     </label>
     {{ ' ' }}
     <Select
@@ -28,8 +28,18 @@ export default {
     Select
   },
   props: {
-    language: String,
-    setLanguage: Function
+    promptText: {
+      type: String,
+      required: true
+    },
+    language: {
+      type: String,
+      required: true
+    },
+    setLanguage: {
+      type: String,
+      required: true
+    }
   },
   computed: {
     languages () {
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 5bc0f516..39149d7f 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -5,6 +5,7 @@
       <ul class="setting-list">
         <li>
           <interface-language-switcher
+            :prompt-text="$t('settings.interfaceLanguage')"
             :language="language"
             :set-language="val => language = val"
           />
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index bee8a7bb..953f17f0 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -8,8 +8,10 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
 import suggestor from 'src/components/emoji_input/suggestor.js'
 import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
 import Checkbox from 'src/components/checkbox/checkbox.vue'
+import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
 import BooleanSetting from '../helpers/boolean_setting.vue'
 import SharedComputedObject from '../helpers/shared_computed_object.js'
+import localeService from 'src/services/locale/locale.service.js'
 
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
@@ -40,7 +42,8 @@ const ProfileTab = {
       banner: null,
       bannerPreview: null,
       background: null,
-      backgroundPreview: null
+      backgroundPreview: null,
+      emailLanguage: this.$store.state.users.currentUser.language
     }
   },
   components: {
@@ -50,7 +53,8 @@ const ProfileTab = {
     Autosuggest,
     ProgressButton,
     Checkbox,
-    BooleanSetting
+    BooleanSetting,
+    InterfaceLanguageSwitcher
   },
   computed: {
     user () {
@@ -111,19 +115,25 @@ const ProfileTab = {
   },
   methods: {
     updateProfile () {
+      const params = {
+        note: this.newBio,
+        locked: this.newLocked,
+        // Backend notation.
+        /* eslint-disable camelcase */
+        display_name: this.newName,
+        fields_attributes: this.newFields.filter(el => el != null),
+        bot: this.bot,
+        show_role: this.showRole,
+        /* eslint-enable camelcase */
+      }
+
+      if (this.emailLanguage) {
+        params.language = localeService.internalToBackendLocale(this.emailLanguage)
+      }
+
       this.$store.state.api.backendInteractor
-        .updateProfile({
-          params: {
-            note: this.newBio,
-            locked: this.newLocked,
-            // Backend notation.
-            /* eslint-disable camelcase */
-            display_name: this.newName,
-            fields_attributes: this.newFields.filter(el => el != null),
-            bot: this.bot,
-            show_role: this.showRole
-            /* eslint-enable camelcase */
-          } }).then((user) => {
+        .updateProfile({ params })
+        .then((user) => {
           this.newFields.splice(user.fields.length)
           merge(this.newFields, user.fields)
           this.$store.commit('addNewUsers', [user])
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index 881016fb..4cd93772 100644
--- a/src/components/settings_modal/tabs/profile_tab.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -89,6 +89,13 @@
           {{ $t('settings.bot') }}
         </Checkbox>
       </p>
+      <p>
+        <interface-language-switcher
+          :prompt-text="$t('settings.email_language')"
+          :language="emailLanguage"
+          :set-language="val => emailLanguage = val"
+        />
+      </p>
       <button
         :disabled="newName && newName.length === 0"
         class="btn button-default"
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 436b8b0a..1b1722ab 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -191,6 +191,7 @@ const updateProfile = ({ credentials, params }) => {
 // homepage
 // location
 // token
+// language
 const register = ({ params, credentials }) => {
   const { nickname, ...rest } = params
   return fetch(MASTODON_REGISTRATION_URL, {
-- 
cgit v1.2.3-70-g09d2


From 069cf3fa87c0d29803275fe66864d5e439cad309 Mon Sep 17 00:00:00 2001
From: HJ <30-hj@users.noreply.git.pleroma.social>
Date: Sun, 22 May 2022 16:38:09 +0000
Subject: Revert "Merge branch 'from/develop/tusooa/lang-opts' into 'develop'"

This reverts merge request !1494
---
 package.json                                       |  1 -
 .../interface_language_switcher.vue                | 38 ++++++++++------------
 src/components/registration/registration.js        | 14 ++------
 src/components/registration/registration.vue       | 12 -------
 src/components/settings_modal/tabs/general_tab.js  |  6 ----
 src/components/settings_modal/tabs/general_tab.vue |  6 +---
 src/components/settings_modal/tabs/profile_tab.js  | 38 ++++++++--------------
 src/components/settings_modal/tabs/profile_tab.vue |  7 ----
 src/i18n/en.json                                   |  4 +--
 src/modules/config.js                              |  5 ---
 src/services/api/api.service.js                    |  1 -
 src/services/locale/locale.service.js              | 27 ++-------------
 yarn.lock                                          |  5 ---
 13 files changed, 37 insertions(+), 127 deletions(-)

(limited to 'src/components/settings_modal/tabs/general_tab.vue')

diff --git a/package.json b/package.json
index 58aeb363..b5ece450 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,6 @@
     "cropperjs": "1.5.12",
     "diff": "3.5.0",
     "escape-html": "1.0.3",
-    "js-cookie": "^3.0.1",
     "localforage": "1.10.0",
     "parse-link-header": "1.0.1",
     "phoenix": "1.6.2",
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index 7ad1fe2e..6d1f83c4 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -1,12 +1,12 @@
 <template>
   <div>
     <label for="interface-language-switcher">
-      {{ promptText }}
+      {{ $t('settings.interfaceLanguage') }}
     </label>
     {{ ' ' }}
     <Select
       id="interface-language-switcher"
-      v-model="controlledLanguage"
+      v-model="language"
     >
       <option
         v-for="lang in languages"
@@ -20,43 +20,39 @@
 </template>
 
 <script>
+import languagesObject from '../../i18n/messages'
 import localeService from '../../services/locale/locale.service.js'
+import ISO6391 from 'iso-639-1'
+import _ from 'lodash'
 import Select from '../select/select.vue'
 
 export default {
   components: {
     Select
   },
-  props: {
-    promptText: {
-      type: String,
-      required: true
-    },
-    language: {
-      type: String,
-      required: true
-    },
-    setLanguage: {
-      type: Function,
-      required: true
-    }
-  },
   computed: {
     languages () {
-      return localeService.languages
+      return _.map(languagesObject.languages, (code) => ({ code: code, name: this.getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
     },
 
-    controlledLanguage: {
-      get: function () { return this.language },
+    language: {
+      get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
       set: function (val) {
-        this.setLanguage(val)
+        this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
       }
     }
   },
 
   methods: {
     getLanguageName (code) {
-      return localeService.getLanguageName(code)
+      const specialLanguageNames = {
+        'ja_easy': 'やさしいにほんご',
+        'zh': '简体中文',
+        'zh_Hant': '繁體中文'
+      }
+      const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
+      const browserLocale = localeService.internalToBrowserLocale(code)
+      return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1)
     }
   }
 }
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index 6eb316d0..a3ef0f04 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -1,8 +1,6 @@
 import useVuelidate from '@vuelidate/core'
 import { required, requiredIf, sameAs } from '@vuelidate/validators'
 import { mapActions, mapState } from 'vuex'
-import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
-import localeService from '../../services/locale/locale.service.js'
 
 const registration = {
   setup () { return { v$: useVuelidate() } },
@@ -13,14 +11,10 @@ const registration = {
       username: '',
       password: '',
       confirm: '',
-      reason: '',
-      language: ''
+      reason: ''
     },
     captcha: {}
   }),
-  components: {
-    InterfaceLanguageSwitcher
-  },
   validations () {
     return {
       user: {
@@ -32,8 +26,7 @@ const registration = {
           required,
           sameAs: sameAs(this.user.password)
         },
-        reason: { required: requiredIf(() => this.accountApprovalRequired) },
-        language: {}
+        reason: { required: requiredIf(() => this.accountApprovalRequired) }
       }
     }
   },
@@ -71,9 +64,6 @@ const registration = {
       this.user.captcha_solution = this.captcha.solution
       this.user.captcha_token = this.captcha.token
       this.user.captcha_answer_data = this.captcha.answer_data
-      if (this.user.language) {
-        this.user.language = localeService.internalToBackendLocale(this.user.language)
-      }
 
       this.v$.$touch()
 
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 6fa70a09..3d409109 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -162,18 +162,6 @@
               </ul>
             </div>
 
-            <div
-              class="form-group"
-              :class="{ 'form-group--error': $v.user.language.$error }"
-            >
-              <interface-language-switcher
-                for="email-language"
-                :prompt-text="$t('registration.email_language')"
-                :language="$v.user.language.$model"
-                :set-language="val => $v.user.language.$model = val"
-              />
-            </div>
-
             <div
               v-if="accountApprovalRequired"
               class="form-group"
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index de925155..62d86176 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -72,12 +72,6 @@ const GeneralTab = {
         !this.$store.state.users.currentUser.background_image
     },
     instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
-    language: {
-      get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
-      set: function (val) {
-        this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
-      }
-    },
     ...SharedComputedObject()
   },
   methods: {
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 39149d7f..a2c6bffa 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -4,11 +4,7 @@
       <h2>{{ $t('settings.interface') }}</h2>
       <ul class="setting-list">
         <li>
-          <interface-language-switcher
-            :prompt-text="$t('settings.interfaceLanguage')"
-            :language="language"
-            :set-language="val => language = val"
-          />
+          <interface-language-switcher />
         </li>
         <li v-if="instanceSpecificPanelPresent">
           <BooleanSetting path="hideISP">
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index 8781bb91..f2b48b4b 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -8,10 +8,8 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
 import suggestor from 'src/components/emoji_input/suggestor.js'
 import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
 import Checkbox from 'src/components/checkbox/checkbox.vue'
-import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
 import BooleanSetting from '../helpers/boolean_setting.vue'
 import SharedComputedObject from '../helpers/shared_computed_object.js'
-import localeService from 'src/services/locale/locale.service.js'
 
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
@@ -42,8 +40,7 @@ const ProfileTab = {
       banner: null,
       bannerPreview: null,
       background: null,
-      backgroundPreview: null,
-      emailLanguage: this.$store.state.users.currentUser.language || ''
+      backgroundPreview: null
     }
   },
   components: {
@@ -53,8 +50,7 @@ const ProfileTab = {
     Autosuggest,
     ProgressButton,
     Checkbox,
-    BooleanSetting,
-    InterfaceLanguageSwitcher
+    BooleanSetting
   },
   computed: {
     user () {
@@ -115,25 +111,19 @@ const ProfileTab = {
   },
   methods: {
     updateProfile () {
-      const params = {
-        note: this.newBio,
-        locked: this.newLocked,
-        // Backend notation.
-        /* eslint-disable camelcase */
-        display_name: this.newName,
-        fields_attributes: this.newFields.filter(el => el != null),
-        bot: this.bot,
-        show_role: this.showRole
-        /* eslint-enable camelcase */
-      }
-
-      if (this.emailLanguage) {
-        params.language = localeService.internalToBackendLocale(this.emailLanguage)
-      }
-
       this.$store.state.api.backendInteractor
-        .updateProfile({ params })
-        .then((user) => {
+        .updateProfile({
+          params: {
+            note: this.newBio,
+            locked: this.newLocked,
+            // Backend notation.
+            /* eslint-disable camelcase */
+            display_name: this.newName,
+            fields_attributes: this.newFields.filter(el => el != null),
+            bot: this.bot,
+            show_role: this.showRole
+            /* eslint-enable camelcase */
+          } }).then((user) => {
           this.newFields.splice(user.fields.length)
           merge(this.newFields, user.fields)
           this.$store.commit('addNewUsers', [user])
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index 4cd93772..881016fb 100644
--- a/src/components/settings_modal/tabs/profile_tab.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -89,13 +89,6 @@
           {{ $t('settings.bot') }}
         </Checkbox>
       </p>
-      <p>
-        <interface-language-switcher
-          :prompt-text="$t('settings.email_language')"
-          :language="emailLanguage"
-          :set-language="val => emailLanguage = val"
-        />
-      </p>
       <button
         :disabled="newName && newName.length === 0"
         class="btn button-default"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index a3174492..733df0a0 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -255,8 +255,7 @@
       "password_required": "cannot be left blank",
       "password_confirmation_required": "cannot be left blank",
       "password_confirmation_match": "should be the same as password"
-    },
-    "email_language": "In which language do you want to receive emails from the server?"
+    }
   },
   "remote_user_resolver": {
     "remote_user_resolver": "Remote user resolver",
@@ -305,7 +304,6 @@
     "avatarRadius": "Avatars",
     "background": "Background",
     "bio": "Bio",
-    "email_language": "Language for receiving emails from the server",
     "block_export": "Block export",
     "block_export_button": "Export your blocks to a csv file",
     "block_import": "Block import",
diff --git a/src/modules/config.js b/src/modules/config.js
index e0401339..13f1045b 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -1,9 +1,5 @@
-import Cookies from 'js-cookie'
 import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
 import messages from '../i18n/messages'
-import localeService from '../services/locale/locale.service.js'
-
-const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
 
 const browserLocale = (window.navigator.language || 'en').split('-')[0]
 
@@ -167,7 +163,6 @@ const config = {
           break
         case 'interfaceLanguage':
           messages.setLanguage(this.getters.i18n, value)
-          Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
           break
       }
     }
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index ca84ba2c..50fa3d50 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -197,7 +197,6 @@ const updateProfile = ({ credentials, params }) => {
 // homepage
 // location
 // token
-// language
 const register = ({ params, credentials }) => {
   const { nickname, ...rest } = params
   return fetch(MASTODON_REGISTRATION_URL, {
diff --git a/src/services/locale/locale.service.js b/src/services/locale/locale.service.js
index 8cef2522..5be99d81 100644
--- a/src/services/locale/locale.service.js
+++ b/src/services/locale/locale.service.js
@@ -1,35 +1,12 @@
-import languagesObject from '../../i18n/messages'
-import ISO6391 from 'iso-639-1'
-import _ from 'lodash'
-
 const specialLanguageCodes = {
   'ja_easy': 'ja',
-  'zh_Hant': 'zh-HANT',
-  'zh': 'zh-Hans'
+  'zh_Hant': 'zh-HANT'
 }
 
 const internalToBrowserLocale = code => specialLanguageCodes[code] || code
 
-const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-')
-
-const getLanguageName = (code) => {
-  const specialLanguageNames = {
-    'ja_easy': 'やさしいにほんご',
-    'zh': '简体中文',
-    'zh_Hant': '繁體中文'
-  }
-  const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
-  const browserLocale = internalToBrowserLocale(code)
-  return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1)
-}
-
-const languages = _.map(languagesObject.languages, (code) => ({ code: code, name: getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
-
 const localeService = {
-  internalToBrowserLocale,
-  internalToBackendLocale,
-  languages,
-  getLanguageName
+  internalToBrowserLocale
 }
 
 export default localeService
diff --git a/yarn.lock b/yarn.lock
index e413b6d1..a898a932 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5722,11 +5722,6 @@ js-base64@^2.1.9:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.0.tgz#42255ba183ab67ce59a0dee640afdc00ab5ae93e"
 
-js-cookie@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
-  integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
-
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
-- 
cgit v1.2.3-70-g09d2


From 9725443e60bc6e147fdc8d195dab048d1d5aa461 Mon Sep 17 00:00:00 2001
From: HJ <30-hj@users.noreply.git.pleroma.social>
Date: Sun, 22 May 2022 16:40:59 +0000
Subject: Revert "Merge branch 'revert-a88abc7e' into 'develop'"

This reverts merge request !1526
---
 package.json                                       |  1 +
 .../interface_language_switcher.vue                | 38 ++++++++++++----------
 src/components/registration/registration.js        | 14 ++++++--
 src/components/registration/registration.vue       | 12 +++++++
 src/components/settings_modal/tabs/general_tab.js  |  6 ++++
 src/components/settings_modal/tabs/general_tab.vue |  6 +++-
 src/components/settings_modal/tabs/profile_tab.js  | 38 ++++++++++++++--------
 src/components/settings_modal/tabs/profile_tab.vue |  7 ++++
 src/i18n/en.json                                   |  4 ++-
 src/modules/config.js                              |  5 +++
 src/services/api/api.service.js                    |  1 +
 src/services/locale/locale.service.js              | 27 +++++++++++++--
 yarn.lock                                          |  5 +++
 13 files changed, 127 insertions(+), 37 deletions(-)

(limited to 'src/components/settings_modal/tabs/general_tab.vue')

diff --git a/package.json b/package.json
index b5ece450..58aeb363 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
     "cropperjs": "1.5.12",
     "diff": "3.5.0",
     "escape-html": "1.0.3",
+    "js-cookie": "^3.0.1",
     "localforage": "1.10.0",
     "parse-link-header": "1.0.1",
     "phoenix": "1.6.2",
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index 6d1f83c4..7ad1fe2e 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -1,12 +1,12 @@
 <template>
   <div>
     <label for="interface-language-switcher">
-      {{ $t('settings.interfaceLanguage') }}
+      {{ promptText }}
     </label>
     {{ ' ' }}
     <Select
       id="interface-language-switcher"
-      v-model="language"
+      v-model="controlledLanguage"
     >
       <option
         v-for="lang in languages"
@@ -20,39 +20,43 @@
 </template>
 
 <script>
-import languagesObject from '../../i18n/messages'
 import localeService from '../../services/locale/locale.service.js'
-import ISO6391 from 'iso-639-1'
-import _ from 'lodash'
 import Select from '../select/select.vue'
 
 export default {
   components: {
     Select
   },
+  props: {
+    promptText: {
+      type: String,
+      required: true
+    },
+    language: {
+      type: String,
+      required: true
+    },
+    setLanguage: {
+      type: Function,
+      required: true
+    }
+  },
   computed: {
     languages () {
-      return _.map(languagesObject.languages, (code) => ({ code: code, name: this.getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
+      return localeService.languages
     },
 
-    language: {
-      get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
+    controlledLanguage: {
+      get: function () { return this.language },
       set: function (val) {
-        this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
+        this.setLanguage(val)
       }
     }
   },
 
   methods: {
     getLanguageName (code) {
-      const specialLanguageNames = {
-        'ja_easy': 'やさしいにほんご',
-        'zh': '简体中文',
-        'zh_Hant': '繁體中文'
-      }
-      const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
-      const browserLocale = localeService.internalToBrowserLocale(code)
-      return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1)
+      return localeService.getLanguageName(code)
     }
   }
 }
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index a3ef0f04..6eb316d0 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -1,6 +1,8 @@
 import useVuelidate from '@vuelidate/core'
 import { required, requiredIf, sameAs } from '@vuelidate/validators'
 import { mapActions, mapState } from 'vuex'
+import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
+import localeService from '../../services/locale/locale.service.js'
 
 const registration = {
   setup () { return { v$: useVuelidate() } },
@@ -11,10 +13,14 @@ const registration = {
       username: '',
       password: '',
       confirm: '',
-      reason: ''
+      reason: '',
+      language: ''
     },
     captcha: {}
   }),
+  components: {
+    InterfaceLanguageSwitcher
+  },
   validations () {
     return {
       user: {
@@ -26,7 +32,8 @@ const registration = {
           required,
           sameAs: sameAs(this.user.password)
         },
-        reason: { required: requiredIf(() => this.accountApprovalRequired) }
+        reason: { required: requiredIf(() => this.accountApprovalRequired) },
+        language: {}
       }
     }
   },
@@ -64,6 +71,9 @@ const registration = {
       this.user.captcha_solution = this.captcha.solution
       this.user.captcha_token = this.captcha.token
       this.user.captcha_answer_data = this.captcha.answer_data
+      if (this.user.language) {
+        this.user.language = localeService.internalToBackendLocale(this.user.language)
+      }
 
       this.v$.$touch()
 
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 3d409109..6fa70a09 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -162,6 +162,18 @@
               </ul>
             </div>
 
+            <div
+              class="form-group"
+              :class="{ 'form-group--error': $v.user.language.$error }"
+            >
+              <interface-language-switcher
+                for="email-language"
+                :prompt-text="$t('registration.email_language')"
+                :language="$v.user.language.$model"
+                :set-language="val => $v.user.language.$model = val"
+              />
+            </div>
+
             <div
               v-if="accountApprovalRequired"
               class="form-group"
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index 62d86176..de925155 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -72,6 +72,12 @@ const GeneralTab = {
         !this.$store.state.users.currentUser.background_image
     },
     instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
+    language: {
+      get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
+      set: function (val) {
+        this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
+      }
+    },
     ...SharedComputedObject()
   },
   methods: {
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index a2c6bffa..39149d7f 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -4,7 +4,11 @@
       <h2>{{ $t('settings.interface') }}</h2>
       <ul class="setting-list">
         <li>
-          <interface-language-switcher />
+          <interface-language-switcher
+            :prompt-text="$t('settings.interfaceLanguage')"
+            :language="language"
+            :set-language="val => language = val"
+          />
         </li>
         <li v-if="instanceSpecificPanelPresent">
           <BooleanSetting path="hideISP">
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index f2b48b4b..8781bb91 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -8,8 +8,10 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
 import suggestor from 'src/components/emoji_input/suggestor.js'
 import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
 import Checkbox from 'src/components/checkbox/checkbox.vue'
+import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
 import BooleanSetting from '../helpers/boolean_setting.vue'
 import SharedComputedObject from '../helpers/shared_computed_object.js'
+import localeService from 'src/services/locale/locale.service.js'
 
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
@@ -40,7 +42,8 @@ const ProfileTab = {
       banner: null,
       bannerPreview: null,
       background: null,
-      backgroundPreview: null
+      backgroundPreview: null,
+      emailLanguage: this.$store.state.users.currentUser.language || ''
     }
   },
   components: {
@@ -50,7 +53,8 @@ const ProfileTab = {
     Autosuggest,
     ProgressButton,
     Checkbox,
-    BooleanSetting
+    BooleanSetting,
+    InterfaceLanguageSwitcher
   },
   computed: {
     user () {
@@ -111,19 +115,25 @@ const ProfileTab = {
   },
   methods: {
     updateProfile () {
+      const params = {
+        note: this.newBio,
+        locked: this.newLocked,
+        // Backend notation.
+        /* eslint-disable camelcase */
+        display_name: this.newName,
+        fields_attributes: this.newFields.filter(el => el != null),
+        bot: this.bot,
+        show_role: this.showRole
+        /* eslint-enable camelcase */
+      }
+
+      if (this.emailLanguage) {
+        params.language = localeService.internalToBackendLocale(this.emailLanguage)
+      }
+
       this.$store.state.api.backendInteractor
-        .updateProfile({
-          params: {
-            note: this.newBio,
-            locked: this.newLocked,
-            // Backend notation.
-            /* eslint-disable camelcase */
-            display_name: this.newName,
-            fields_attributes: this.newFields.filter(el => el != null),
-            bot: this.bot,
-            show_role: this.showRole
-            /* eslint-enable camelcase */
-          } }).then((user) => {
+        .updateProfile({ params })
+        .then((user) => {
           this.newFields.splice(user.fields.length)
           merge(this.newFields, user.fields)
           this.$store.commit('addNewUsers', [user])
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index 881016fb..4cd93772 100644
--- a/src/components/settings_modal/tabs/profile_tab.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -89,6 +89,13 @@
           {{ $t('settings.bot') }}
         </Checkbox>
       </p>
+      <p>
+        <interface-language-switcher
+          :prompt-text="$t('settings.email_language')"
+          :language="emailLanguage"
+          :set-language="val => emailLanguage = val"
+        />
+      </p>
       <button
         :disabled="newName && newName.length === 0"
         class="btn button-default"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 733df0a0..a3174492 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -255,7 +255,8 @@
       "password_required": "cannot be left blank",
       "password_confirmation_required": "cannot be left blank",
       "password_confirmation_match": "should be the same as password"
-    }
+    },
+    "email_language": "In which language do you want to receive emails from the server?"
   },
   "remote_user_resolver": {
     "remote_user_resolver": "Remote user resolver",
@@ -304,6 +305,7 @@
     "avatarRadius": "Avatars",
     "background": "Background",
     "bio": "Bio",
+    "email_language": "Language for receiving emails from the server",
     "block_export": "Block export",
     "block_export_button": "Export your blocks to a csv file",
     "block_import": "Block import",
diff --git a/src/modules/config.js b/src/modules/config.js
index 13f1045b..e0401339 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -1,5 +1,9 @@
+import Cookies from 'js-cookie'
 import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
 import messages from '../i18n/messages'
+import localeService from '../services/locale/locale.service.js'
+
+const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
 
 const browserLocale = (window.navigator.language || 'en').split('-')[0]
 
@@ -163,6 +167,7 @@ const config = {
           break
         case 'interfaceLanguage':
           messages.setLanguage(this.getters.i18n, value)
+          Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
           break
       }
     }
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 50fa3d50..ca84ba2c 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -197,6 +197,7 @@ const updateProfile = ({ credentials, params }) => {
 // homepage
 // location
 // token
+// language
 const register = ({ params, credentials }) => {
   const { nickname, ...rest } = params
   return fetch(MASTODON_REGISTRATION_URL, {
diff --git a/src/services/locale/locale.service.js b/src/services/locale/locale.service.js
index 5be99d81..8cef2522 100644
--- a/src/services/locale/locale.service.js
+++ b/src/services/locale/locale.service.js
@@ -1,12 +1,35 @@
+import languagesObject from '../../i18n/messages'
+import ISO6391 from 'iso-639-1'
+import _ from 'lodash'
+
 const specialLanguageCodes = {
   'ja_easy': 'ja',
-  'zh_Hant': 'zh-HANT'
+  'zh_Hant': 'zh-HANT',
+  'zh': 'zh-Hans'
 }
 
 const internalToBrowserLocale = code => specialLanguageCodes[code] || code
 
+const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-')
+
+const getLanguageName = (code) => {
+  const specialLanguageNames = {
+    'ja_easy': 'やさしいにほんご',
+    'zh': '简体中文',
+    'zh_Hant': '繁體中文'
+  }
+  const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
+  const browserLocale = internalToBrowserLocale(code)
+  return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1)
+}
+
+const languages = _.map(languagesObject.languages, (code) => ({ code: code, name: getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
+
 const localeService = {
-  internalToBrowserLocale
+  internalToBrowserLocale,
+  internalToBackendLocale,
+  languages,
+  getLanguageName
 }
 
 export default localeService
diff --git a/yarn.lock b/yarn.lock
index a898a932..e413b6d1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5722,6 +5722,11 @@ js-base64@^2.1.9:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.0.tgz#42255ba183ab67ce59a0dee640afdc00ab5ae93e"
 
+js-cookie@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
+  integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
+
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
-- 
cgit v1.2.3-70-g09d2

