aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/interface_language_switcher/interface_language_switcher.vue80
-rw-r--r--src/components/settings_modal/tabs/profile_tab.js4
-rw-r--r--src/i18n/en.json4
-rw-r--r--src/i18n/eo.json2
-rw-r--r--src/i18n/messages.js26
-rw-r--r--src/i18n/zh.json49
-rw-r--r--src/modules/config.js5
-rw-r--r--src/modules/instance.js9
-rw-r--r--src/services/locale/locale.service.js5
9 files changed, 155 insertions, 29 deletions
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index 6997f149..c9e7506b 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/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>
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index ff2ccef2..b2d8fed2 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: {
@@ -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
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ca022c82..c8de6851 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -339,6 +339,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",
diff --git a/src/i18n/eo.json b/src/i18n/eo.json
index 3596065c..e013edee 100644
--- a/src/i18n/eo.json
+++ b/src/i18n/eo.json
@@ -603,7 +603,7 @@
"use_websockets": "Uzi teĥnikaron «websockets» (tuja ĝisdatigo)",
"mention_link_display_full_for_remote": "plene nur je uzantoj foraj (ekz. {'@'}zozo{'@'}ekzemplo.org)",
"expert_mode": "Montri altnivelajn",
- "setting_server_side": "Ĉi tiu agordo estas ligita al via profilo, kaj efektiviĝon en ĉiuj viaj salutoj kaj klientoj",
+ "setting_server_side": "Ĉi tiu agordo estas ligita al via profilo, kaj efektiviĝos en ĉiuj viaj salutoj kaj klientoj",
"post_look_feel": "Aspekto de afiŝoj",
"mention_links": "Menciaj ligiloj",
"email_language": "Lingvo de leteroj ricevotaj de la servilo",
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index 74a89ca8..849d98fd 100644
--- a/src/i18n/messages.js
+++ b/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
}
}
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index cf5f384c..8f0b04bd 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -53,7 +53,13 @@
"direct": "私讯",
"private": "仅关注者",
"unlisted": "列外"
- }
+ },
+ "scroll_to_top": "滚动至顶",
+ "generic_error_message": "发生一个错误:{0}",
+ "never_show_again": "不再显示",
+ "undo": "撤销",
+ "yes": "是",
+ "no": "否"
},
"image_cropper": {
"crop_picture": "裁剪图片",
@@ -109,7 +115,12 @@
"chats": "聊天",
"timelines": "时间线",
"bookmarks": "书签",
- "home_timeline": "主页时间线"
+ "home_timeline": "主页时间线",
+ "lists": "列表",
+ "edit_finish": "完成编辑",
+ "mobile_notifications": "打开通知(有未读的)",
+ "mobile_notifications_close": "关闭通知",
+ "announcements": "公告"
},
"notifications": {
"broken_favorite": "未知的状态,正在搜索中…",
@@ -890,7 +901,18 @@
"search_emoji": "搜索表情符号",
"emoji": "表情符号",
"load_all": "加载所有表情符号(共 {emojiAmount} 个)",
- "load_all_hint": "最先加载的 {saneAmount} 表情符号,加载全部表情符号可能会带来性能问题。"
+ "load_all_hint": "最先加载的 {saneAmount} 表情符号,加载全部表情符号可能会带来性能问题。",
+ "unicode_groups": {
+ "flags": "旗帜",
+ "food-and-drink": "饮食",
+ "objects": "物件",
+ "people-and-body": "人和身体",
+ "symbols": "符号",
+ "travel-and-places": "旅行和地点",
+ "activities": "活动",
+ "animals-and-nature": "动物和自然"
+ },
+ "regional_indicator": "地区指示符 {letter}"
},
"about": {
"mrf": {
@@ -958,5 +980,26 @@
"delete": "删除",
"message_user": "发消息给 {nickname}",
"you": "你:"
+ },
+ "announcements": {
+ "page_header": "公告",
+ "title": "公告",
+ "mark_as_read_action": "标为已读",
+ "post_form_header": "发布公告",
+ "post_placeholder": "在这里输入公告内容...",
+ "post_action": "发布",
+ "post_error": "错误:{error}",
+ "close_error": "关闭",
+ "delete_action": "删除",
+ "start_time_prompt": "起始时间: ",
+ "end_time_prompt": "终止时间: ",
+ "all_day_prompt": "这是全天的事件",
+ "published_time_display": "发表于 {time}",
+ "start_time_display": "开始于 {time}",
+ "end_time_display": "结束于 {time}",
+ "edit_action": "编辑",
+ "submit_edit_action": "提交",
+ "cancel_edit_action": "取消",
+ "inactive_message": "这个公告不活跃"
}
}
diff --git a/src/modules/config.js b/src/modules/config.js
index 8e6b2c3d..eb33f95f 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -193,7 +193,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)
diff --git a/src/modules/instance.js b/src/modules/instance.js
index ce958907..c7579162 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -295,8 +295,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
+ }
}
}))
},
diff --git a/src/services/locale/locale.service.js b/src/services/locale/locale.service.js
index d3389785..a4af8b90 100644
--- a/src/services/locale/locale.service.js
+++ b/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
}