From 179511be17a621a0775158ce19bb964972a6399f Mon Sep 17 00:00:00 2001 From: Axolotle Date: Fri, 28 Aug 2020 18:54:18 +0200 Subject: [PATCH] add 'settings' store with action to mutate locales & add lazy loading of translations --- app/src/App.vue | 14 +++++-- app/src/i18n/helpers.js | 64 ++++++++++++++++++++++++++++++++ app/src/i18n/index.js | 25 +++++++++++++ app/src/i18n/supportedLocales.js | 26 +++++++++++++ app/src/main.js | 2 +- app/src/store/index.js | 2 + app/src/store/settings.js | 53 ++++++++++++++++++++++++++ 7 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 app/src/i18n/helpers.js create mode 100644 app/src/i18n/index.js create mode 100644 app/src/i18n/supportedLocales.js create mode 100644 app/src/store/settings.js diff --git a/app/src/App.vue b/app/src/App.vue index 5380b939..91d6cd92 100644 --- a/app/src/App.vue +++ b/app/src/App.vue @@ -68,16 +68,19 @@ import { mapGetters } from 'vuex' export default { name: 'App', - data: () => { + + data () { return { // isReady blocks the rendering of the rooter-view until we have a true info // about the connected state of the user. isReady: false } }, + computed: { ...mapGetters(['connected', 'yunohost']) }, + methods: { async logout () { this.$store.dispatch('LOGOUT').then(() => { @@ -85,9 +88,14 @@ export default { }) } }, - // This hook is only triggered at page reload so the value of state.connected - // always come from the localStorage + + // This hook is only triggered at page first load async created () { + // Save user locales in store + const { locale, fallbackLocale } = this.$i18n + this.$store.dispatch('INIT_LOCALES', { locale, fallbackLocale }) + + // From this hook the value of `connected` always come from the localStorage. if (!this.connected) { // user is not connected: allow the login view to be rendered. this.isReady = true diff --git a/app/src/i18n/helpers.js b/app/src/i18n/helpers.js new file mode 100644 index 00000000..2deb0ca7 --- /dev/null +++ b/app/src/i18n/helpers.js @@ -0,0 +1,64 @@ +import i18n from '@/i18n' +import supportedLocales from './supportedLocales' + +const loadedLanguages = [] + +/** + * Returns the first two supported locales that can be found in the `localStorage` or + * in the user browser settings. + * + * @return {string[]} + */ +function getDefaultLocales () { + const locale = localStorage.getItem('locale') + const fallbackLocale = localStorage.getItem('fallbackLocale') + + if (locale && fallbackLocale) return [locale, fallbackLocale] + + const navigatorLocales = navigator.languages || [navigator.language] + const defaultLocales = [] + const supported = Object.keys(supportedLocales) + for (const locale of navigatorLocales) { + if (supported.includes(locale) && !defaultLocales.includes(locale)) { + defaultLocales.push(locale) + } else { + const lang = locale.split('-')[0] + if (supported.includes(lang) && !defaultLocales.includes(lang)) { + defaultLocales.push(lang) + } + } + if (defaultLocales.length === 2) break + } + + return defaultLocales +} + +function updateDocumentLocale (locale) { + document.documentElement.lang = locale + // FIXME can't currently change document direction easily since bootstrap still doesn't handle rtl. + // document.dir = locale === 'ar' ? 'rtl' : 'ltr' +} + +/** + * Loads a translation file and adds its content to the i18n plugin `messages`. + * + * @return {Promise} Promise that resolve the given locale string + */ +function loadLocaleMessages (locale) { + if (loadedLanguages.includes(locale)) { + return Promise.resolve(locale) + } + return import( + /* webpackChunkName: "lang-[request]" */ `@/locales/${locale}.json` + ).then(messages => { + i18n.setLocaleMessage(locale, messages.default) + loadedLanguages.push(locale) + return locale + }) +} + +export { + getDefaultLocales, + updateDocumentLocale, + loadLocaleMessages +} diff --git a/app/src/i18n/index.js b/app/src/i18n/index.js new file mode 100644 index 00000000..3811d76c --- /dev/null +++ b/app/src/i18n/index.js @@ -0,0 +1,25 @@ +/** + * i18n plugin module. + * @module i18n + */ + +import Vue from 'vue' +import VueI18n from 'vue-i18n' +import { getDefaultLocales, loadLocaleMessages } from './helpers' + +// Plugin Initialization +Vue.use(VueI18n) + +// Get defined locales from `localStorage` or `navigator` +const [locale, fallbackLocale] = getDefaultLocales() + +export default new VueI18n({ + locale, + fallbackLocale: fallbackLocale ? [fallbackLocale, 'en'] : ['en'], + messages: {} +}) + +// Load default locales translations files +loadLocaleMessages(locale) +loadLocaleMessages(fallbackLocale) +loadLocaleMessages('en') diff --git a/app/src/i18n/supportedLocales.js b/app/src/i18n/supportedLocales.js new file mode 100644 index 00000000..fdf3f869 --- /dev/null +++ b/app/src/i18n/supportedLocales.js @@ -0,0 +1,26 @@ +export default { + ar: 'عربي', + bn_BD: 'বাংলা', + br: 'Brezhoneg', + ca: 'Català', + de: 'Deutsch', + el: 'Eλληνικά', + en: 'English', + eo: 'Esperanto', + es: 'Español', + eu: 'Euskara', + fr: 'Français', + hi: 'हिन्दी', + hu: 'Magyar', + it: 'Italiano', + nb_NO: 'Norsk bokmål', + ne: 'नेपाली', + nl: 'Nederlands', + oc: 'Occitan', + pl: 'Polski', + pt: 'Português', + ru: 'Русский', + sv: 'Svenska', + tr: 'Türkçe', + zh_Hans: '简化字' +} diff --git a/app/src/main.js b/app/src/main.js index de3426e6..fefd6083 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -1,7 +1,7 @@ import Vue from 'vue' import App from './App.vue' import './plugins/bootstrap-vue' -import i18n from './plugins/i18n' +import i18n from './i18n' import router from './plugins/router' import store from './store' diff --git a/app/src/store/index.js b/app/src/store/index.js index bbcd6983..6a8cf4a8 100644 --- a/app/src/store/index.js +++ b/app/src/store/index.js @@ -2,6 +2,7 @@ import Vue from 'vue' import Vuex from 'vuex' import info from './info' +import settings from './settings' import data from './data' Vue.use(Vuex) @@ -9,6 +10,7 @@ Vue.use(Vuex) export default new Vuex.Store({ modules: { info, + settings, data } }) diff --git a/app/src/store/settings.js b/app/src/store/settings.js new file mode 100644 index 00000000..e49cc7bf --- /dev/null +++ b/app/src/store/settings.js @@ -0,0 +1,53 @@ +/** + * Settings module store. + * @module store/settings + */ + +import i18n from '@/i18n' +import { loadLocaleMessages, updateDocumentLocale } from '@/i18n/helpers' + +export default { + state: { + locale: undefined, + fallbackLocale: undefined + }, + + mutations: { + 'SET_LOCALE' (state, locale) { + localStorage.setItem('locale', locale) + state.locale = locale + }, + + 'SET_FALLBACK_LOCALE' (state, locale) { + localStorage.setItem('fallbackLocale', locale) + state.fallbackLocale = locale + } + }, + + actions: { + 'UPDATE_LOCALE' ({ commit }, locale) { + loadLocaleMessages(locale).then(() => { + i18n.locale = locale + updateDocumentLocale(locale) + commit('SET_LOCALE', locale) + }) + }, + + 'UPDATE_FALLBACK_LOCALE' ({ commit }, locale) { + loadLocaleMessages(locale).then(() => { + i18n.fallbackLocale = [locale, 'en'] + commit('SET_FALLBACK_LOCALE', locale) + }) + }, + + 'INIT_LOCALES' ({ commit }, { locale, fallbackLocale }) { + commit('SET_LOCALE', locale) + commit('SET_FALLBACK_LOCALE', fallbackLocale[0]) + } + }, + + getters: { + locale: state => (state.locale), + fallbackLocale: state => (state.fallbackLocale) + } +}