Merge branch 'from/develop/tusooa/lang-opts' into 'develop'

Add language options

See merge request pleroma/pleroma-fe!1494
This commit is contained in:
HJ 2022-05-22 16:35:51 +00:00
commit a88abc7ee3
13 changed files with 127 additions and 37 deletions

View file

@ -31,6 +31,7 @@
"cropperjs": "1.5.12", "cropperjs": "1.5.12",
"diff": "3.5.0", "diff": "3.5.0",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"js-cookie": "^3.0.1",
"localforage": "1.10.0", "localforage": "1.10.0",
"parse-link-header": "1.0.1", "parse-link-header": "1.0.1",
"phoenix": "1.6.2", "phoenix": "1.6.2",

View file

@ -1,12 +1,12 @@
<template> <template>
<div> <div>
<label for="interface-language-switcher"> <label for="interface-language-switcher">
{{ $t('settings.interfaceLanguage') }} {{ promptText }}
</label> </label>
{{ ' ' }} {{ ' ' }}
<Select <Select
id="interface-language-switcher" id="interface-language-switcher"
v-model="language" v-model="controlledLanguage"
> >
<option <option
v-for="lang in languages" v-for="lang in languages"
@ -20,39 +20,43 @@
</template> </template>
<script> <script>
import languagesObject from '../../i18n/messages'
import localeService from '../../services/locale/locale.service.js' import localeService from '../../services/locale/locale.service.js'
import ISO6391 from 'iso-639-1'
import _ from 'lodash'
import Select from '../select/select.vue' import Select from '../select/select.vue'
export default { export default {
components: { components: {
Select Select
}, },
props: {
promptText: {
type: String,
required: true
},
language: {
type: String,
required: true
},
setLanguage: {
type: Function,
required: true
}
},
computed: { computed: {
languages () { languages () {
return _.map(languagesObject.languages, (code) => ({ code: code, name: this.getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name)) return localeService.languages
}, },
language: { controlledLanguage: {
get: function () { return this.$store.getters.mergedConfig.interfaceLanguage }, get: function () { return this.language },
set: function (val) { set: function (val) {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) this.setLanguage(val)
} }
} }
}, },
methods: { methods: {
getLanguageName (code) { getLanguageName (code) {
const specialLanguageNames = { return localeService.getLanguageName(code)
'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)
} }
} }
} }

View file

@ -1,6 +1,8 @@
import useVuelidate from '@vuelidate/core' import useVuelidate from '@vuelidate/core'
import { required, requiredIf, sameAs } from '@vuelidate/validators' import { required, requiredIf, sameAs } from '@vuelidate/validators'
import { mapActions, mapState } from 'vuex' 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 = { const registration = {
setup () { return { v$: useVuelidate() } }, setup () { return { v$: useVuelidate() } },
@ -11,10 +13,14 @@ const registration = {
username: '', username: '',
password: '', password: '',
confirm: '', confirm: '',
reason: '' reason: '',
language: ''
}, },
captcha: {} captcha: {}
}), }),
components: {
InterfaceLanguageSwitcher
},
validations () { validations () {
return { return {
user: { user: {
@ -26,7 +32,8 @@ const registration = {
required, required,
sameAs: sameAs(this.user.password) 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_solution = this.captcha.solution
this.user.captcha_token = this.captcha.token this.user.captcha_token = this.captcha.token
this.user.captcha_answer_data = this.captcha.answer_data this.user.captcha_answer_data = this.captcha.answer_data
if (this.user.language) {
this.user.language = localeService.internalToBackendLocale(this.user.language)
}
this.v$.$touch() this.v$.$touch()

View file

@ -162,6 +162,18 @@
</ul> </ul>
</div> </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 <div
v-if="accountApprovalRequired" v-if="accountApprovalRequired"
class="form-group" class="form-group"

View file

@ -72,6 +72,12 @@ const GeneralTab = {
!this.$store.state.users.currentUser.background_image !this.$store.state.users.currentUser.background_image
}, },
instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable }, 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() ...SharedComputedObject()
}, },
methods: { methods: {

View file

@ -4,7 +4,11 @@
<h2>{{ $t('settings.interface') }}</h2> <h2>{{ $t('settings.interface') }}</h2>
<ul class="setting-list"> <ul class="setting-list">
<li> <li>
<interface-language-switcher /> <interface-language-switcher
:prompt-text="$t('settings.interfaceLanguage')"
:language="language"
:set-language="val => language = val"
/>
</li> </li>
<li v-if="instanceSpecificPanelPresent"> <li v-if="instanceSpecificPanelPresent">
<BooleanSetting path="hideISP"> <BooleanSetting path="hideISP">

View file

@ -8,8 +8,10 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
import suggestor from 'src/components/emoji_input/suggestor.js' import suggestor from 'src/components/emoji_input/suggestor.js'
import Autosuggest from 'src/components/autosuggest/autosuggest.vue' import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
import Checkbox from 'src/components/checkbox/checkbox.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 BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js' 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 { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -40,7 +42,8 @@ const ProfileTab = {
banner: null, banner: null,
bannerPreview: null, bannerPreview: null,
background: null, background: null,
backgroundPreview: null backgroundPreview: null,
emailLanguage: this.$store.state.users.currentUser.language || ''
} }
}, },
components: { components: {
@ -50,7 +53,8 @@ const ProfileTab = {
Autosuggest, Autosuggest,
ProgressButton, ProgressButton,
Checkbox, Checkbox,
BooleanSetting BooleanSetting,
InterfaceLanguageSwitcher
}, },
computed: { computed: {
user () { user () {
@ -111,19 +115,25 @@ const ProfileTab = {
}, },
methods: { methods: {
updateProfile () { 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 this.$store.state.api.backendInteractor
.updateProfile({ .updateProfile({ params })
params: { .then((user) => {
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) this.newFields.splice(user.fields.length)
merge(this.newFields, user.fields) merge(this.newFields, user.fields)
this.$store.commit('addNewUsers', [user]) this.$store.commit('addNewUsers', [user])

View file

@ -89,6 +89,13 @@
{{ $t('settings.bot') }} {{ $t('settings.bot') }}
</Checkbox> </Checkbox>
</p> </p>
<p>
<interface-language-switcher
:prompt-text="$t('settings.email_language')"
:language="emailLanguage"
:set-language="val => emailLanguage = val"
/>
</p>
<button <button
:disabled="newName && newName.length === 0" :disabled="newName && newName.length === 0"
class="btn button-default" class="btn button-default"

View file

@ -255,7 +255,8 @@
"password_required": "cannot be left blank", "password_required": "cannot be left blank",
"password_confirmation_required": "cannot be left blank", "password_confirmation_required": "cannot be left blank",
"password_confirmation_match": "should be the same as password" "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": "Remote user resolver", "remote_user_resolver": "Remote user resolver",
@ -304,6 +305,7 @@
"avatarRadius": "Avatars", "avatarRadius": "Avatars",
"background": "Background", "background": "Background",
"bio": "Bio", "bio": "Bio",
"email_language": "Language for receiving emails from the server",
"block_export": "Block export", "block_export": "Block export",
"block_export_button": "Export your blocks to a csv file", "block_export_button": "Export your blocks to a csv file",
"block_import": "Block import", "block_import": "Block import",

View file

@ -1,5 +1,9 @@
import Cookies from 'js-cookie'
import { setPreset, applyTheme } from '../services/style_setter/style_setter.js' import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages' 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] const browserLocale = (window.navigator.language || 'en').split('-')[0]
@ -163,6 +167,7 @@ const config = {
break break
case 'interfaceLanguage': case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value) messages.setLanguage(this.getters.i18n, value)
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
break break
} }
} }

View file

@ -197,6 +197,7 @@ const updateProfile = ({ credentials, params }) => {
// homepage // homepage
// location // location
// token // token
// language
const register = ({ params, credentials }) => { const register = ({ params, credentials }) => {
const { nickname, ...rest } = params const { nickname, ...rest } = params
return fetch(MASTODON_REGISTRATION_URL, { return fetch(MASTODON_REGISTRATION_URL, {

View file

@ -1,12 +1,35 @@
import languagesObject from '../../i18n/messages'
import ISO6391 from 'iso-639-1'
import _ from 'lodash'
const specialLanguageCodes = { const specialLanguageCodes = {
'ja_easy': 'ja', 'ja_easy': 'ja',
'zh_Hant': 'zh-HANT' 'zh_Hant': 'zh-HANT',
'zh': 'zh-Hans'
} }
const internalToBrowserLocale = code => specialLanguageCodes[code] || code 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 = { const localeService = {
internalToBrowserLocale internalToBrowserLocale,
internalToBackendLocale,
languages,
getLanguageName
} }
export default localeService export default localeService

View file

@ -5722,6 +5722,11 @@ js-base64@^2.1.9:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.0.tgz#42255ba183ab67ce59a0dee640afdc00ab5ae93e" 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: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"