Browse Source

Merge branch 'from/develop/tusooa/multi-iface-lang' into 'develop'

Multiple interface languages support

See merge request pleroma/pleroma-fe!1568
HJ 1 year ago
parent
commit
a9716701be

+ 64 - 16
src/components/interface_language_switcher/interface_language_switcher.vue

@@ -1,21 +1,44 @@
 <template>
-  <div>
-    <label for="interface-language-switcher">
+  <div class="interface-language-switcher">
+    <label>
       {{ promptText }}
     </label>
-    {{ ' ' }}
-    <Select
-      id="interface-language-switcher"
-      v-model="controlledLanguage"
-    >
-      <option
-        v-for="lang in languages"
-        :key="lang.code"
-        :value="lang.code"
+    <ul class="setting-list">
+      <li
+        v-for="index of controlledLanguage.keys()"
+        :key="index"
       >
-        {{ lang.name }}
-      </option>
-    </Select>
+        <label>
+          {{ index === 0 ? $t('settings.primary_language') : $tc('settings.fallback_language', index, { index }) }}
+          <Select
+            class="language-select"
+            :model-value="controlledLanguage[index]"
+            @update:modelValue="val => setLanguageAt(index, val)"
+          >
+            <option
+              v-for="lang in languages"
+              :key="lang.code"
+              :value="lang.code"
+            >
+              {{ lang.name }}
+            </option>
+          </Select>
+        </label>
+        <button
+          v-if="controlledLanguage.length > 1 && index !== 0"
+          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>
+    </ul>
   </div>
 </template>
 
@@ -34,7 +57,7 @@ export default {
       required: true
     },
     language: {
-      type: String,
+      type: [Array, String],
       required: true
     },
     setLanguage: {
@@ -48,7 +71,9 @@ export default {
     },
 
     controlledLanguage: {
-      get: function () { return this.language },
+      get: function () {
+        return Array.isArray(this.language) ? this.language : [this.language]
+      },
       set: function (val) {
         this.setLanguage(val)
       }
@@ -58,7 +83,30 @@ export default {
   methods: {
     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>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.interface-language-switcher {
+  .language-select {
+    margin-right: 1em;
+  }
+}
+</style>

+ 2 - 2
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: {
@@ -130,7 +130,7 @@ const ProfileTab = {
       }
 
       if (this.emailLanguage) {
-        params.language = localeService.internalToBackendLocale(this.emailLanguage)
+        params.language = localeService.internalToBackendLocaleMulti(this.emailLanguage)
       }
 
       this.$store.state.api.backendInteractor

+ 4 - 0
src/i18n/en.json

@@ -335,6 +335,10 @@
     "select_all": "Select all"
   },
   "settings": {
+    "add_language": "Add fallback language",
+    "remove_language": "Remove",
+    "primary_language": "Primary language:",
+    "fallback_language": "Fallback language {index}:",
     "app_name": "App name",
     "expert_mode": "Show advanced",
     "save": "Save changes",

+ 22 - 4
src/i18n/messages.js

@@ -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
 // 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'
 
+const ULTIMATE_FALLBACK_LOCALE = 'en'
+
 const hasLanguageFile = (code) => languages.includes(code)
 
 const loadLanguageFile = (code) => {
@@ -25,11 +28,26 @@ const messages = {
     en: require('./en.json').default
   },
   setLanguage: async (i18n, language) => {
-    if (hasLanguageFile(language)) {
-      const messages = await loadLanguageFile(language)
-      i18n.setLocaleMessage(language, messages.default)
+    const languages = (Array.isArray(language) ? language : [language]).filter(k => k)
+
+    if (!languages.includes(ULTIMATE_FALLBACK_LOCALE)) {
+      languages.push(ULTIMATE_FALLBACK_LOCALE)
+    }
+    const [first, ...rest] = languages
+
+    if (first === i18n.locale && isEqual(rest, i18n.fallbackLocale)) {
+      return
     }
-    i18n.locale = language
+
+    for (const lang of languages) {
+      if (hasLanguageFile(lang)) {
+        const messages = await loadLanguageFile(lang)
+        i18n.setLocaleMessage(lang, messages.default)
+      }
+    }
+
+    i18n.fallbackLocale = rest
+    i18n.locale = first
   }
 }
 

+ 4 - 1
src/modules/config.js

@@ -184,7 +184,10 @@ const config = {
         case 'interfaceLanguage':
           messages.setLanguage(this.getters.i18n, value)
           dispatch('loadUnicodeEmojiData', value)
-          Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
+          Cookies.set(
+            BACKEND_LANGUAGE_COOKIE_NAME,
+            localeService.internalToBackendLocaleMulti(value)
+          )
           break
         case 'thirdColumnMode':
           dispatch('setLayoutWidth', undefined)

+ 7 - 2
src/modules/instance.js

@@ -286,8 +286,13 @@ const instance = {
         langList
           .map(async lang => {
             if (!state.unicodeEmojiAnnotations[lang]) {
-              const annotations = await loadAnnotations(lang)
-              commit('setUnicodeEmojiAnnotations', { lang, annotations })
+              try {
+                const annotations = await loadAnnotations(lang)
+                commit('setUnicodeEmojiAnnotations', { lang, annotations })
+              } catch (e) {
+                console.warn(`Error loading unicode emoji annotations for ${lang}: `, e)
+                // ignore
+              }
             }
           }))
     },

+ 5 - 0
src/services/locale/locale.service.js

@@ -11,6 +11,10 @@ const specialLanguageCodes = {
 const internalToBrowserLocale = code => specialLanguageCodes[code] || code
 
 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 specialLanguageNames = {
@@ -28,6 +32,7 @@ const languages = _.map(languagesObject.languages, (code) => ({ code, name: getL
 const localeService = {
   internalToBrowserLocale,
   internalToBackendLocale,
+  internalToBackendLocaleMulti,
   languages,
   getLanguageName
 }