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: [PATCH 1/6] Set userLanguage cookie when setting interface language --- package.json | 1 + .../interface_language_switcher.vue | 26 +++++++----------- .../settings_modal/tabs/general_tab.js | 6 +++++ .../settings_modal/tabs/general_tab.vue | 5 +++- src/modules/config.js | 6 +++++ src/services/locale/locale.service.js | 27 +++++++++++++++++-- yarn.lock | 5 ++++ 7 files changed, 57 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index e9a246b3..ab9043d3 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..757ec01f 100644 --- a/src/components/interface_language_switcher/interface_language_switcher.vue +++ b/src/components/interface_language_switcher/interface_language_switcher.vue @@ -6,7 +6,7 @@ {{ ' ' }} <Select id="interface-language-switcher" - v-model="language" + v-model="controlledLanguage" > <option v-for="lang in languages" @@ -20,39 +20,33 @@ </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: { + language: String, + setLanguage: Function + }, 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/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..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"> diff --git a/src/modules/config.js b/src/modules/config.js index ff5ef270..eeefd896 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -1,5 +1,10 @@ +import { set, delete as del } from 'vue' +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] @@ -162,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/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 042d87ac..cd96543b 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" 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: [PATCH 2/6] Add config opts for email language --- .../interface_language_switcher.vue | 16 ++++++-- .../settings_modal/tabs/general_tab.vue | 1 + .../settings_modal/tabs/profile_tab.js | 38 ++++++++++++------- .../settings_modal/tabs/profile_tab.vue | 7 ++++ src/services/api/api.service.js | 1 + 5 files changed, 46 insertions(+), 17 deletions(-) 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, { From 1d36ea5ec58c8df870a0c6c1ec267b78b8db28f0 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu <tusooa@kazv.moe> Date: Sat, 26 Mar 2022 13:03:23 -0400 Subject: [PATCH 3/6] Add email language option to registration form --- .../interface_language_switcher.vue | 2 +- src/components/registration/registration.js | 14 ++++++++++++-- src/components/registration/registration.vue | 12 ++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue index 078535dc..7ad1fe2e 100644 --- a/src/components/interface_language_switcher/interface_language_switcher.vue +++ b/src/components/interface_language_switcher/interface_language_switcher.vue @@ -37,7 +37,7 @@ export default { required: true }, setLanguage: { - type: String, + type: Function, required: true } }, diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js index a3ef0f04..e3f6b875 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" From 093c85d006fac375bcdbf38964da9c011e4f00f3 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu <tusooa@kazv.moe> Date: Sat, 26 Mar 2022 13:03:39 -0400 Subject: [PATCH 4/6] Add English translation for language options --- src/i18n/en.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index f8336e5c..76a29426 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -254,7 +254,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", @@ -303,6 +304,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", From 3633ea66d4c6f362510788f3ae7ae4cad020629a Mon Sep 17 00:00:00 2001 From: Tusooa Zhu <tusooa@kazv.moe> Date: Sat, 26 Mar 2022 13:08:22 -0400 Subject: [PATCH 5/6] Make lint happy --- src/components/registration/registration.js | 2 +- src/components/settings_modal/tabs/profile_tab.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js index e3f6b875..6eb316d0 100644 --- a/src/components/registration/registration.js +++ b/src/components/registration/registration.js @@ -14,7 +14,7 @@ const registration = { password: '', confirm: '', reason: '', - language: '', + language: '' }, captcha: {} }), diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index 953f17f0..9a8628f0 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -43,7 +43,7 @@ const ProfileTab = { bannerPreview: null, background: null, backgroundPreview: null, - emailLanguage: this.$store.state.users.currentUser.language + emailLanguage: this.$store.state.users.currentUser.language || '' } }, components: { @@ -123,7 +123,7 @@ const ProfileTab = { display_name: this.newName, fields_attributes: this.newFields.filter(el => el != null), bot: this.bot, - show_role: this.showRole, + show_role: this.showRole /* eslint-enable camelcase */ } From f57171d7b97d344d1a30df4cf6e5a5b29a8e2b5d Mon Sep 17 00:00:00 2001 From: Tusooa Zhu <tusooa@kazv.moe> Date: Fri, 29 Apr 2022 20:36:56 -0400 Subject: [PATCH 6/6] Make lint happy --- src/modules/config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/config.js b/src/modules/config.js index eeefd896..10d1a90a 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -1,4 +1,3 @@ -import { set, delete as del } from 'vue' import Cookies from 'js-cookie' import { setPreset, applyTheme } from '../services/style_setter/style_setter.js' import messages from '../i18n/messages'