Add support for multiple interface languages
This commit is contained in:
parent
25e628efe2
commit
b7e9373965
|
@ -1,12 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="interface-language-switcher">
|
||||||
<label for="interface-language-switcher">
|
<label>
|
||||||
{{ promptText }}
|
{{ promptText }}
|
||||||
</label>
|
</label>
|
||||||
{{ ' ' }}
|
<ol>
|
||||||
|
<li
|
||||||
|
v-for="index of controlledLanguage.keys()"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
<Select
|
<Select
|
||||||
id="interface-language-switcher"
|
class="language-select"
|
||||||
v-model="controlledLanguage"
|
:model-value="controlledLanguage[index]"
|
||||||
|
@update:modelValue="val => setLanguageAt(index, val)"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="lang in languages"
|
v-for="lang in languages"
|
||||||
|
@ -16,6 +21,19 @@
|
||||||
{{ lang.name }}
|
{{ lang.name }}
|
||||||
</option>
|
</option>
|
||||||
</Select>
|
</Select>
|
||||||
|
<button
|
||||||
|
v-if="controlledLanguage.length > 1"
|
||||||
|
class="button-default btn"
|
||||||
|
@click="() => removeLanguageAt(index)"
|
||||||
|
>{{ $t('settings.remove_language') }}</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="button-default btn"
|
||||||
|
@click="addLanguage"
|
||||||
|
>{{ $t('settings.add_language') }}</button>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -34,7 +52,7 @@ export default {
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
language: {
|
language: {
|
||||||
type: String,
|
type: [Array, String],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
setLanguage: {
|
setLanguage: {
|
||||||
|
@ -48,7 +66,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
controlledLanguage: {
|
controlledLanguage: {
|
||||||
get: function () { return this.language },
|
get: function () {
|
||||||
|
return Array.isArray(this.language) ? this.language : [this.language]
|
||||||
|
},
|
||||||
set: function (val) {
|
set: function (val) {
|
||||||
this.setLanguage(val)
|
this.setLanguage(val)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +78,30 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
getLanguageName (code) {
|
getLanguageName (code) {
|
||||||
return localeService.getLanguageName(code)
|
return localeService.getLanguageName(code)
|
||||||
|
},
|
||||||
|
addLanguage () {
|
||||||
|
this.controlledLanguage = [...this.controlledLanguage, '']
|
||||||
|
},
|
||||||
|
setLanguageAt (index, val) {
|
||||||
|
const lang = [...this.controlledLanguage]
|
||||||
|
lang[index] = val
|
||||||
|
this.controlledLanguage = lang
|
||||||
|
},
|
||||||
|
removeLanguageAt (index) {
|
||||||
|
const lang = [...this.controlledLanguage]
|
||||||
|
lang.splice(index, 1)
|
||||||
|
this.controlledLanguage = lang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.interface-language-switcher {
|
||||||
|
.language-select {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -43,7 +43,7 @@ const ProfileTab = {
|
||||||
bannerPreview: null,
|
bannerPreview: null,
|
||||||
background: null,
|
background: null,
|
||||||
backgroundPreview: null,
|
backgroundPreview: null,
|
||||||
emailLanguage: this.$store.state.users.currentUser.language || ''
|
emailLanguage: this.$store.state.users.currentUser.language || ['']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
@ -130,7 +130,7 @@ const ProfileTab = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.emailLanguage) {
|
if (this.emailLanguage) {
|
||||||
params.language = localeService.internalToBackendLocale(this.emailLanguage)
|
params.language = localeService.internalToBackendLocaleMulti(this.emailLanguage)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.state.api.backendInteractor
|
this.$store.state.api.backendInteractor
|
||||||
|
|
|
@ -7,8 +7,11 @@
|
||||||
// sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json
|
// sed -i -e "s/'//gm" -e 's/"/\\"/gm' -re 's/^( +)(.+?): ((.+?))?(,?)(\{?)$/\1"\2": "\4"/gm' -e 's/\"\{\"/{/g' -e 's/,"$/",/g' file.json
|
||||||
// There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry.
|
// There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry.
|
||||||
|
|
||||||
|
import { isEqual } from 'lodash'
|
||||||
import { languages, langCodeToJsonName } from './languages.js'
|
import { languages, langCodeToJsonName } from './languages.js'
|
||||||
|
|
||||||
|
const ULTIMATE_FALLBACK_LOCALE = 'en'
|
||||||
|
|
||||||
const hasLanguageFile = (code) => languages.includes(code)
|
const hasLanguageFile = (code) => languages.includes(code)
|
||||||
|
|
||||||
const loadLanguageFile = (code) => {
|
const loadLanguageFile = (code) => {
|
||||||
|
@ -25,11 +28,26 @@ const messages = {
|
||||||
en: require('./en.json').default
|
en: require('./en.json').default
|
||||||
},
|
},
|
||||||
setLanguage: async (i18n, language) => {
|
setLanguage: async (i18n, language) => {
|
||||||
if (hasLanguageFile(language)) {
|
const languages = (Array.isArray(language) ? language : [language]).filter(k => k)
|
||||||
const messages = await loadLanguageFile(language)
|
|
||||||
i18n.setLocaleMessage(language, messages.default)
|
if (!languages.includes(ULTIMATE_FALLBACK_LOCALE)) {
|
||||||
|
languages.push(ULTIMATE_FALLBACK_LOCALE)
|
||||||
}
|
}
|
||||||
i18n.locale = language
|
const [first, ...rest] = languages
|
||||||
|
|
||||||
|
if (first === i18n.locale && isEqual(rest, i18n.fallbackLocale)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const lang of languages) {
|
||||||
|
if (hasLanguageFile(lang)) {
|
||||||
|
const messages = await loadLanguageFile(lang)
|
||||||
|
i18n.setLocaleMessage(lang, messages.default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i18n.fallbackLocale = rest
|
||||||
|
i18n.locale = first
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -184,7 +184,10 @@ const config = {
|
||||||
case 'interfaceLanguage':
|
case 'interfaceLanguage':
|
||||||
messages.setLanguage(this.getters.i18n, value)
|
messages.setLanguage(this.getters.i18n, value)
|
||||||
dispatch('loadUnicodeEmojiData', value)
|
dispatch('loadUnicodeEmojiData', value)
|
||||||
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
|
Cookies.set(
|
||||||
|
BACKEND_LANGUAGE_COOKIE_NAME,
|
||||||
|
localeService.internalToBackendLocaleMulti(value)
|
||||||
|
)
|
||||||
break
|
break
|
||||||
case 'thirdColumnMode':
|
case 'thirdColumnMode':
|
||||||
dispatch('setLayoutWidth', undefined)
|
dispatch('setLayoutWidth', undefined)
|
||||||
|
|
|
@ -11,6 +11,10 @@ const specialLanguageCodes = {
|
||||||
const internalToBrowserLocale = code => specialLanguageCodes[code] || code
|
const internalToBrowserLocale = code => specialLanguageCodes[code] || code
|
||||||
|
|
||||||
const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-')
|
const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-')
|
||||||
|
const internalToBackendLocaleMulti = codes => {
|
||||||
|
const langs = Array.isArray(codes) ? codes : [codes]
|
||||||
|
return langs.map(internalToBackendLocale).join(',')
|
||||||
|
}
|
||||||
|
|
||||||
const getLanguageName = (code) => {
|
const getLanguageName = (code) => {
|
||||||
const specialLanguageNames = {
|
const specialLanguageNames = {
|
||||||
|
@ -28,6 +32,7 @@ const languages = _.map(languagesObject.languages, (code) => ({ code, name: getL
|
||||||
const localeService = {
|
const localeService = {
|
||||||
internalToBrowserLocale,
|
internalToBrowserLocale,
|
||||||
internalToBackendLocale,
|
internalToBackendLocale,
|
||||||
|
internalToBackendLocaleMulti,
|
||||||
languages,
|
languages,
|
||||||
getLanguageName
|
getLanguageName
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue