mirror of
https://github.com/requarks/wiki.git
synced 2026-08-26 13:17:10 -05:00
feat: admin language switcher
This commit is contained in:
@@ -5,7 +5,7 @@ export default {
|
||||
Query: {
|
||||
async locales(obj, args, context, info) {
|
||||
let remoteLocales = await WIKI.cache.get('locales')
|
||||
let localLocales = await WIKI.db.locales.query().select('code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt', 'completeness')
|
||||
let localLocales = await WIKI.db.locales.query().select('code', 'isRTL', 'language', 'name', 'nativeName', 'createdAt', 'updatedAt', 'completeness')
|
||||
remoteLocales = remoteLocales || localLocales
|
||||
return _.map(remoteLocales, rl => {
|
||||
let isInstalled = _.some(localLocales, ['code', rl.code])
|
||||
|
||||
@@ -31,6 +31,7 @@ type LocalizationLocale {
|
||||
installDate: Date
|
||||
isInstalled: Boolean
|
||||
isRTL: Boolean
|
||||
language: String
|
||||
name: String
|
||||
nativeName: String
|
||||
region: String
|
||||
|
||||
+46
-44
@@ -5,14 +5,15 @@ router-view
|
||||
<script setup>
|
||||
import { nextTick, onMounted, reactive, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { setCssVar, useQuasar } from 'quasar'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
|
||||
import { useCommonStore } from './stores/common'
|
||||
import { useFlagsStore } from 'src/stores/flags'
|
||||
import { useSiteStore } from 'src/stores/site'
|
||||
import { useUserStore } from 'src/stores/user'
|
||||
import { setCssVar, useQuasar } from 'quasar'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
|
||||
/* global siteConfig */
|
||||
|
||||
@@ -22,6 +23,7 @@ const $q = useQuasar()
|
||||
|
||||
// STORES
|
||||
|
||||
const commonStore = useCommonStore()
|
||||
const flagsStore = useFlagsStore()
|
||||
const siteStore = useSiteStore()
|
||||
const userStore = useUserStore()
|
||||
@@ -54,6 +56,25 @@ watch(() => userStore.cvd, () => {
|
||||
applyTheme()
|
||||
})
|
||||
|
||||
watch(() => commonStore.locale, applyLocale)
|
||||
|
||||
// LOCALE
|
||||
|
||||
async function applyLocale (locale) {
|
||||
if (!i18n.availableLocales.includes(locale)) {
|
||||
try {
|
||||
i18n.setLocaleMessage(locale, await commonStore.fetchLocaleStrings(locale))
|
||||
} catch (err) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: `Failed to load ${locale} locale strings.`,
|
||||
caption: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
i18n.locale.value = locale
|
||||
}
|
||||
|
||||
// THEME
|
||||
|
||||
async function applyTheme () {
|
||||
@@ -89,35 +110,6 @@ async function applyTheme () {
|
||||
}
|
||||
}
|
||||
|
||||
// LOCALE
|
||||
|
||||
async function fetchLocaleStrings (locale) {
|
||||
try {
|
||||
const resp = await APOLLO_CLIENT.query({
|
||||
query: gql`
|
||||
query fetchLocaleStrings (
|
||||
$locale: String!
|
||||
) {
|
||||
localeStrings (
|
||||
locale: $locale
|
||||
)
|
||||
}
|
||||
`,
|
||||
fetchPolicy: 'cache-first',
|
||||
variables: {
|
||||
locale
|
||||
}
|
||||
})
|
||||
return resp?.data?.localeStrings
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: 'Failed to load locale strings.'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// INIT SITE STORE
|
||||
|
||||
if (typeof siteConfig !== 'undefined') {
|
||||
@@ -128,30 +120,40 @@ if (typeof siteConfig !== 'undefined') {
|
||||
applyTheme()
|
||||
}
|
||||
|
||||
// ROUTE GUARDS
|
||||
|
||||
router.beforeEach(async (to, from) => {
|
||||
siteStore.routerLoading = true
|
||||
// System Flags
|
||||
commonStore.routerLoading = true
|
||||
|
||||
// -> System Flags
|
||||
if (!flagsStore.loaded) {
|
||||
flagsStore.load()
|
||||
}
|
||||
// Site Info
|
||||
|
||||
// -> Site Info
|
||||
if (!siteStore.id) {
|
||||
console.info('No pre-cached site config. Loading site info...')
|
||||
await siteStore.loadSite(window.location.hostname)
|
||||
console.info(`Using Site ID ${siteStore.id}`)
|
||||
}
|
||||
// Locales
|
||||
if (!i18n.availableLocales.includes('en')) {
|
||||
i18n.setLocaleMessage('en', await fetchLocaleStrings('en'))
|
||||
|
||||
// -> Locale
|
||||
if (!commonStore.desiredLocale || !siteStore.locales.active.includes(commonStore.desiredLocale)) {
|
||||
commonStore.setLocale(siteStore.locales.primary)
|
||||
} else {
|
||||
applyLocale(commonStore.desiredLocale)
|
||||
}
|
||||
// User Auth
|
||||
|
||||
// -> User Auth
|
||||
await userStore.refreshAuth()
|
||||
// User Profile
|
||||
|
||||
// -> User Profile
|
||||
if (userStore.authenticated && !userStore.profileLoaded) {
|
||||
console.info(`Refreshing user ${userStore.id} profile...`)
|
||||
await userStore.refreshProfile()
|
||||
}
|
||||
// Page Permissions
|
||||
|
||||
// -> Page Permissions
|
||||
await userStore.fetchPagePermissions(to.path)
|
||||
})
|
||||
|
||||
@@ -177,7 +179,7 @@ router.afterEach(() => {
|
||||
applyTheme()
|
||||
document.querySelector('.init-loading').remove()
|
||||
}
|
||||
siteStore.routerLoading = false
|
||||
commonStore.routerLoading = false
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -3,10 +3,14 @@ import { ApolloClient, InMemoryCache } from '@apollo/client/core'
|
||||
import { setContext } from '@apollo/client/link/context'
|
||||
import { createUploadLink } from 'apollo-upload-client'
|
||||
|
||||
export default boot(({ app, store }) => {
|
||||
import { useUserStore } from 'src/stores/user'
|
||||
|
||||
export default boot(({ app }) => {
|
||||
const userStore = useUserStore()
|
||||
|
||||
// Authentication Link
|
||||
const authLink = setContext(async (req, { headers }) => {
|
||||
const token = store.state.value.user.token
|
||||
const token = userStore.token
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
|
||||
+5
-1
@@ -1,10 +1,14 @@
|
||||
import { boot } from 'quasar/wrappers'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import { useCommonStore } from 'src/stores/common'
|
||||
|
||||
export default boot(({ app }) => {
|
||||
const commonStore = useCommonStore()
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
locale: commonStore.locale || 'en',
|
||||
fallbackLocale: 'en',
|
||||
fallbackWarn: false,
|
||||
messages: {}
|
||||
|
||||
@@ -68,7 +68,7 @@ q-header.bg-header.text-white.site-header(
|
||||
q-space
|
||||
transition(name='syncing')
|
||||
q-spinner-tail(
|
||||
v-show='siteStore.routerLoading'
|
||||
v-show='commonStore.routerLoading'
|
||||
color='accent'
|
||||
size='24px'
|
||||
)
|
||||
@@ -130,6 +130,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
import { useCommonStore } from 'src/stores/common'
|
||||
import { useSiteStore } from 'src/stores/site'
|
||||
import { useUserStore } from 'src/stores/user'
|
||||
|
||||
@@ -139,6 +140,7 @@ const $q = useQuasar()
|
||||
|
||||
// STORES
|
||||
|
||||
const commonStore = useCommonStore()
|
||||
const siteStore = useSiteStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
|
||||
@@ -18,11 +18,25 @@ q-layout.admin(view='hHh Lpr lff')
|
||||
q-space
|
||||
transition(name='syncing')
|
||||
q-spinner-tail(
|
||||
v-show='siteStore.routerLoading'
|
||||
v-show='commonStore.routerLoading'
|
||||
color='accent'
|
||||
size='24px'
|
||||
)
|
||||
q-btn.q-ml-md(flat, dense, icon='las la-times-circle', label='Exit' color='pink', to='/')
|
||||
q-btn.q-ml-md(flat, dense, icon='las la-times-circle', :label='t(`common.actions.exit`)' color='pink', to='/')
|
||||
q-btn.q-ml-md(flat, dense, icon='las la-language', :label='commonStore.locale' color='grey-4')
|
||||
q-menu.translucent-menu(auto-close, anchor='bottom right', self='top right')
|
||||
q-list(separator)
|
||||
q-item(
|
||||
v-for='lang of adminStore.locales'
|
||||
clickable
|
||||
@click='commonStore.setLocale(lang.code)'
|
||||
)
|
||||
q-item-section(side)
|
||||
q-avatar(rounded, :color='lang.code === commonStore.locale ? `secondary` : `primary`', text-color='white', size='sm')
|
||||
.text-caption.text-uppercase: strong {{ lang.language }}
|
||||
q-item-section
|
||||
q-item-label {{ lang.name }}
|
||||
q-item-label(caption) {{ lang.nativeName }}
|
||||
account-menu
|
||||
q-drawer.admin-sidebar(v-model='leftDrawerOpen', show-if-above, bordered)
|
||||
q-scroll-area.admin-nav(
|
||||
@@ -108,6 +122,9 @@ q-layout.admin(view='hHh Lpr lff')
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-ssd.svg')
|
||||
q-item-section {{ t('admin.storage.title') }}
|
||||
q-item-section(side)
|
||||
//- TODO: Reflect site storage status
|
||||
status-light(:color='true ? `positive` : `warning`', :pulse='false')
|
||||
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/theme`', v-ripple, active-class='bg-primary text-white', v-if='userStore.can(`manage:sites`) || userStore.can(`manage:theme`)')
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-paint-roller.svg')
|
||||
@@ -159,7 +176,7 @@ q-layout.admin(view='hHh Lpr lff')
|
||||
q-icon(name='img:/_assets/icons/fluent-message-settings.svg')
|
||||
q-item-section {{ t('admin.mail.title') }}
|
||||
q-item-section(side)
|
||||
status-light(:color='adminStore.info.isMailConfigured ? `positive` : `warning`')
|
||||
status-light(:color='adminStore.info.isMailConfigured ? `positive` : `warning`', :pulse='!adminStore.info.isMailConfigured')
|
||||
q-item(to='/_admin/rendering', v-ripple, active-class='bg-primary text-white')
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-rich-text-converter.svg')
|
||||
@@ -169,7 +186,7 @@ q-layout.admin(view='hHh Lpr lff')
|
||||
q-icon(name='img:/_assets/icons/fluent-bot.svg')
|
||||
q-item-section {{ t('admin.scheduler.title') }}
|
||||
q-item-section(side)
|
||||
status-light(:color='adminStore.info.isSchedulerHealthy ? `positive` : `warning`')
|
||||
status-light(:color='adminStore.info.isSchedulerHealthy ? `positive` : `warning`', :pulse='!adminStore.info.isSchedulerHealthy')
|
||||
q-item(to='/_admin/security', v-ripple, active-class='bg-primary text-white')
|
||||
q-item-section(avatar)
|
||||
q-icon(name='img:/_assets/icons/fluent-protect.svg')
|
||||
@@ -223,6 +240,7 @@ import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useAdminStore } from 'src/stores/admin'
|
||||
import { useCommonStore } from 'src/stores/common'
|
||||
import { useFlagsStore } from 'src/stores/flags'
|
||||
import { useSiteStore } from 'src/stores/site'
|
||||
import { useUserStore } from 'src/stores/user'
|
||||
@@ -244,6 +262,7 @@ const $q = useQuasar()
|
||||
// STORES
|
||||
|
||||
const adminStore = useAdminStore()
|
||||
const commonStore = useCommonStore()
|
||||
const flagsStore = useFlagsStore()
|
||||
const siteStore = useSiteStore()
|
||||
const userStore = useUserStore()
|
||||
@@ -266,13 +285,6 @@ useMeta({
|
||||
// DATA
|
||||
|
||||
const leftDrawerOpen = ref(true)
|
||||
const overlayIsShown = ref(false)
|
||||
const search = ref('')
|
||||
const user = reactive({
|
||||
name: 'John Doe',
|
||||
email: 'test@example.com',
|
||||
picture: null
|
||||
})
|
||||
const thumbStyle = {
|
||||
right: '1px',
|
||||
borderRadius: '5px',
|
||||
@@ -292,6 +304,9 @@ const siteSectionShown = computed(() => {
|
||||
const usersSectionShown = computed(() => {
|
||||
return userStore.can('manage:groups') || userStore.can('manage:users')
|
||||
})
|
||||
const overlayIsShown = computed(() => {
|
||||
return Boolean(adminStore.overlay)
|
||||
})
|
||||
|
||||
// WATCHERS
|
||||
|
||||
@@ -308,9 +323,6 @@ watch(() => adminStore.sites, (newValue) => {
|
||||
})
|
||||
}
|
||||
})
|
||||
watch(() => adminStore.overlay, (newValue) => {
|
||||
overlayIsShown.value = !!newValue
|
||||
})
|
||||
watch(() => adminStore.currentSiteId, (newValue) => {
|
||||
if (newValue && route.params.siteid !== newValue) {
|
||||
router.push({ params: { siteid: newValue } })
|
||||
@@ -325,6 +337,7 @@ onMounted(async () => {
|
||||
return
|
||||
}
|
||||
|
||||
adminStore.fetchLocales()
|
||||
await adminStore.fetchSites()
|
||||
if (route.params.siteid) {
|
||||
adminStore.$patch({
|
||||
|
||||
+28
-13
@@ -35,24 +35,20 @@ export const useAdminStore = defineStore('admin', {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async fetchSites () {
|
||||
async fetchLocales () {
|
||||
const resp = await APOLLO_CLIENT.query({
|
||||
query: gql`
|
||||
query getSites {
|
||||
sites {
|
||||
id
|
||||
hostname
|
||||
isEnabled
|
||||
title
|
||||
query getAdminLocales {
|
||||
locales {
|
||||
code
|
||||
language
|
||||
name
|
||||
nativeName
|
||||
}
|
||||
}
|
||||
`,
|
||||
fetchPolicy: 'network-only'
|
||||
`
|
||||
})
|
||||
this.sites = cloneDeep(resp?.data?.sites ?? [])
|
||||
if (!this.currentSiteId) {
|
||||
this.currentSiteId = this.sites[0].id
|
||||
}
|
||||
this.locales = cloneDeep(resp?.data?.locales ?? [])
|
||||
},
|
||||
async fetchInfo () {
|
||||
const resp = await APOLLO_CLIENT.query({
|
||||
@@ -78,6 +74,25 @@ export const useAdminStore = defineStore('admin', {
|
||||
this.info.isApiEnabled = clone(resp?.data?.apiState ?? false)
|
||||
this.info.isMailConfigured = clone(resp?.data?.systemInfo?.isMailConfigured ?? false)
|
||||
this.info.isSchedulerHealthy = clone(resp?.data?.systemInfo?.isSchedulerHealthy ?? false)
|
||||
},
|
||||
async fetchSites () {
|
||||
const resp = await APOLLO_CLIENT.query({
|
||||
query: gql`
|
||||
query getSites {
|
||||
sites {
|
||||
id
|
||||
hostname
|
||||
isEnabled
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
fetchPolicy: 'network-only'
|
||||
})
|
||||
this.sites = cloneDeep(resp?.data?.sites ?? [])
|
||||
if (!this.currentSiteId) {
|
||||
this.currentSiteId = this.sites[0].id
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const useCommonStore = defineStore('common', {
|
||||
state: () => ({
|
||||
routerLoading: false,
|
||||
locale: localStorage.getItem('locale') || 'en',
|
||||
desiredLocale: localStorage.getItem('locale')
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
async fetchLocaleStrings (locale) {
|
||||
try {
|
||||
const resp = await APOLLO_CLIENT.query({
|
||||
query: gql`
|
||||
query fetchLocaleStrings (
|
||||
$locale: String!
|
||||
) {
|
||||
localeStrings (
|
||||
locale: $locale
|
||||
)
|
||||
}
|
||||
`,
|
||||
fetchPolicy: 'cache-first',
|
||||
variables: {
|
||||
locale
|
||||
}
|
||||
})
|
||||
return resp?.data?.localeStrings
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
throw err
|
||||
}
|
||||
},
|
||||
setLocale (locale) {
|
||||
this.$patch({
|
||||
locale,
|
||||
desiredLocale: locale
|
||||
})
|
||||
localStorage.setItem('locale', locale)
|
||||
}
|
||||
}
|
||||
})
|
||||
+27
-14
@@ -6,9 +6,7 @@ import { useUserStore } from './user'
|
||||
|
||||
export const useSiteStore = defineStore('site', {
|
||||
state: () => ({
|
||||
routerLoading: false,
|
||||
id: null,
|
||||
useLocales: false,
|
||||
hostname: '',
|
||||
company: '',
|
||||
contentLicense: '',
|
||||
@@ -39,6 +37,10 @@ export const useSiteStore = defineStore('site', {
|
||||
markdown: false,
|
||||
wysiwyg: false
|
||||
},
|
||||
locales: {
|
||||
primary: 'en',
|
||||
active: ['en']
|
||||
},
|
||||
theme: {
|
||||
dark: false,
|
||||
injectCSS: '',
|
||||
@@ -84,6 +86,9 @@ export const useSiteStore = defineStore('site', {
|
||||
opacity: isDark ? 0.25 : 1
|
||||
}
|
||||
}
|
||||
},
|
||||
useLocales: (state) => {
|
||||
return state.locales?.active?.length > 1
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -104,20 +109,9 @@ export const useSiteStore = defineStore('site', {
|
||||
hostname: $hostname
|
||||
exact: false
|
||||
) {
|
||||
id
|
||||
hostname
|
||||
title
|
||||
description
|
||||
logoText
|
||||
company
|
||||
contentLicense
|
||||
footerExtra
|
||||
features {
|
||||
profile
|
||||
ratingsMode
|
||||
reasonForChange
|
||||
search
|
||||
}
|
||||
description
|
||||
editors {
|
||||
asciidoc {
|
||||
isActive
|
||||
@@ -129,6 +123,20 @@ export const useSiteStore = defineStore('site', {
|
||||
isActive
|
||||
}
|
||||
}
|
||||
features {
|
||||
profile
|
||||
ratingsMode
|
||||
reasonForChange
|
||||
search
|
||||
}
|
||||
footerExtra
|
||||
hostname
|
||||
id
|
||||
locales {
|
||||
primary
|
||||
active
|
||||
}
|
||||
logoText
|
||||
theme {
|
||||
dark
|
||||
colorPrimary
|
||||
@@ -144,6 +152,7 @@ export const useSiteStore = defineStore('site', {
|
||||
baseFont
|
||||
contentFont
|
||||
}
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
@@ -171,6 +180,10 @@ export const useSiteStore = defineStore('site', {
|
||||
markdown: clone(siteInfo.editors.markdown.isActive),
|
||||
wysiwyg: clone(siteInfo.editors.wysiwyg.isActive)
|
||||
},
|
||||
locales: {
|
||||
primary: clone(siteInfo.locales.primary),
|
||||
active: clone(siteInfo.locales.active)
|
||||
},
|
||||
theme: {
|
||||
...this.theme,
|
||||
...clone(siteInfo.theme)
|
||||
|
||||
Reference in New Issue
Block a user