feat: admin locales UI + auto locales update + auto version check

pull/6775/head
NGPixel 1 year ago
parent 2587778e52
commit 57c9d37fda
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -514,7 +514,9 @@ export async function up (knex) {
{
key: 'update',
value: {
locales: true
lastCheckedAt: null,
version: null,
versionDate: null
}
},
{

@ -356,6 +356,7 @@
"admin.instances.lastSeen": "Last Seen",
"admin.instances.subtitle": "View a list of active instances",
"admin.instances.title": "Instances",
"admin.locale.active": "Active Locales",
"admin.locale.activeNamespaces": "Active Namespaces",
"admin.locale.autoUpdate.hint": "Automatically download updates to this locale as they become available.",
"admin.locale.autoUpdate.hintWithNS": "Automatically download updates to all namespaced locales enabled below.",
@ -368,6 +369,8 @@
"admin.locale.download": "Download",
"admin.locale.downloadNew": "Install New Locale",
"admin.locale.downloadTitle": "Download Locale",
"admin.locale.forcePrefix": "Force Locale Prefix",
"admin.locale.forcePrefixHint": "Paths without a locale code will always be redirected to the primary locale.",
"admin.locale.name": "Name",
"admin.locale.namespaces.hint": "Enables multiple language versions of the same page.",
"admin.locale.namespaces.label": "Multilingual Namespaces",
@ -375,6 +378,8 @@
"admin.locale.namespacingPrefixWarning.subtitle": "Paths without a locale code will be automatically redirected to the base locale defined above.",
"admin.locale.namespacingPrefixWarning.title": "The locale code will be prefixed to all paths. (e.g. /{langCode}/page-name)",
"admin.locale.nativeName": "Native Name",
"admin.locale.primary": "Primary Locale",
"admin.locale.primaryHint": "The locale to use as default / fallback for this site.",
"admin.locale.rtl": "RTL",
"admin.locale.settings": "Locale Settings",
"admin.locale.sideload": "Sideload Locale Package",

@ -2,7 +2,15 @@ export async function task (payload) {
WIKI.logger.info('Checking for latest version...')
try {
// TODO: Fetch latest version
const resp = await fetch('https://api.github.com/repos/requarks/wiki/releases/latest').then(r => r.json())
WIKI.logger.info(`Latest version is ${resp.tag_name}.`)
await WIKI.db.knex('settings').where('key', 'update').update({
value: {
lastCheckedAt: (new Date).toISOString(),
version: resp.tag_name,
versionDate: resp.published_at
}
})
WIKI.logger.info('Checked for latest version: [ COMPLETED ]')
} catch (err) {

@ -1,8 +1,48 @@
import { setTimeout } from 'node:timers/promises'
export async function task (payload) {
if (WIKI.config.update?.locales === false) {
return
}
WIKI.logger.info('Fetching latest localization data...')
try {
// TODO: Fetch locale updates
const metadata = await fetch('https://github.com/requarks/wiki-locales/raw/main/locales/metadata.json').then(r => r.json())
for (const lang of metadata.languages) {
// -> Build filename
const langFilenameParts = [lang.language]
if (lang.region) {
langFilenameParts.push(lang.region)
}
if (lang.script) {
langFilenameParts.push(lang.script)
}
const langFilename = langFilenameParts.join('-')
WIKI.logger.debug(`Fetching updates for language ${langFilename}...`)
const strings = await fetch(`https://github.com/requarks/wiki-locales/raw/main/locales/${langFilename}.json`).then(r => r.json())
if (strings) {
await WIKI.db.knex('locales').insert({
code: langFilename,
name: lang.name,
nativeName: lang.localizedName,
language: lang.language,
region: lang.region,
script: lang.script,
isRTL: lang.isRtl,
strings
}).onConflict('code').merge({
strings,
updatedAt: new Date()
})
}
WIKI.logger.debug(`Updated strings for language ${langFilename}.`)
await setTimeout(100)
}
WIKI.logger.info('Fetched latest localization data: [ COMPLETED ]')
} catch (err) {

@ -29,7 +29,7 @@ export default boot(({ app }) => {
}
// -> Refresh Token
if (!userStore.isTokenValid()) {
if (!userStore.isTokenValid({ minutes: 1 })) {
if (!fetching) {
refreshPromise = new Promise((resolve, reject) => {
(async () => {

@ -7,15 +7,6 @@ q-page.admin-locale
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.locale.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.locale.subtitle') }}
.col-auto.flex
q-btn.q-mr-md(
icon='las la-download'
:label='t(`admin.locale.downloadNew`)'
unelevated
color='primary'
:disabled='state.loading > 0'
@click='installNewLocale'
)
q-separator.q-mr-md(vertical)
q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle'
flat
@ -55,73 +46,65 @@ q-page.admin-locale
q-item
blueprint-icon(icon='translation')
q-item-section
q-item-label {{state.namespacing ? t(`admin.locale.base.labelWithNS`) : t(`admin.locale.base.label`)}}
q-item-label(caption) {{t(`admin.locale.base.hint`)}}
q-item-label {{t(`admin.locale.primary`)}}
q-item-label(caption) {{t(`admin.locale.primaryHint`)}}
q-item-section
q-select(
outlined
v-model='state.primary'
:options='installedLocales'
:options='state.locales'
option-value='code'
option-label='name'
emit-value
map-options
dense
:aria-label='t(`admin.locale.base.label`)'
:aria-label='t(`admin.locale.primary`)'
)
q-separator.q-my-sm(inset)
q-item(tag='label')
blueprint-icon(icon='unit')
q-item-section
q-item-label {{t(`admin.locale.namespaces.label`)}}
q-item-label(caption) {{t(`admin.locale.namespaces.hint`)}}
q-item-label {{t(`admin.locale.forcePrefix`)}}
q-item-label(caption) {{t(`admin.locale.forcePrefixHint`)}}
q-item-section(avatar)
q-toggle(
v-model='state.namespacing'
v-model='state.forcePrefix'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.locale.namespaces.label`)'
:aria-label='t(`admin.locale.forcePrefixHint`)'
)
q-item
q-item-section
q-card.bg-info.text-white.rounded-borders(flat)
q-card-section.items-center(horizontal)
q-card-section.col-auto.q-pr-none
q-icon(name='las la-info-circle', size='sm')
q-card-section
span {{ t('admin.locale.namespacingPrefixWarning.title', { langCode: state.selectedLocale }) }}
.text-caption.text-yellow-1 {{ t('admin.locale.namespacingPrefixWarning.subtitle') }}
.col-12.col-lg-5
//- -----------------------
//- Namespacing
//- Active Locales
//- -----------------------
q-card.q-pb-sm(v-if='state.namespacing')
q-card.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{t('admin.locale.activeNamespaces')}}
.text-subtitle1 {{t('admin.locale.active')}}
.text-caption(:class='$q.dark.isActive ? `text-grey-4` : `text-grey-7`') Select the locales that can be used on this site.
q-item(
v-for='(lc, idx) of installedLocales'
v-for='(lc, idx) of state.locales'
:key='lc.code'
:tag='lc.code !== state.selectedLocale ? `label` : null'
)
blueprint-icon(:text='lc.code')
blueprint-icon(:text='lc.language')
q-item-section
q-item-label {{lc.name}}
q-item-label(caption) {{lc.nativeName}}
q-item-label {{lc.nativeName}}
q-item-label(caption) {{lc.name}} ({{lc.code}})
q-item-section(avatar)
q-toggle(
:disable='lc.code === state.selectedLocale'
v-model='state.namespaces'
:disable='lc.code === state.primary'
v-model='state.active'
:val='lc.code'
color='primary'
:color='lc.code === state.primary ? `secondary` : `primary`'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='lc.name'
)
.q-pa-md.text-center.gt-md(v-else)
.col-12.col-lg-5
.q-pa-md.text-center
img(src='/_assets/illustrations/undraw_world.svg', style='width: 80%;')
//- q-separator.q-my-sm(inset)
@ -150,7 +133,7 @@ q-page.admin-locale
<script setup>
import gql from 'graphql-tag'
import { cloneDeep, filter } from 'lodash-es'
import { cloneDeep, sortBy } from 'lodash-es'
import LocaleInstallDialog from '../components/LocaleInstallDialog.vue'
@ -186,15 +169,10 @@ const state = reactive({
loading: 0,
locales: [],
primary: 'en',
forcePrefix: false,
active: []
})
// COMPUTED
const installedLocales = computed(() => {
return filter(state.locales, ['isInstalled', true])
})
// WATCHERS
watch(() => adminStore.currentSiteId, (newValue) => {
@ -226,9 +204,8 @@ async function load () {
completeness
code
createdAt
isInstalled
installDate
isRTL
language
name
nativeName
region
@ -241,7 +218,9 @@ async function load () {
id
locales {
primary
active
active {
code
}
}
}
}
@ -251,9 +230,9 @@ async function load () {
},
fetchPolicy: 'network-only'
})
state.locales = cloneDeep(resp?.data?.locales)
state.locales = sortBy(cloneDeep(resp?.data?.locales), ['nativeName', 'name'])
state.primary = cloneDeep(resp?.data?.siteById?.locales?.primary)
state.active = cloneDeep(resp?.data?.siteById?.locales?.active ?? [])
state.active = cloneDeep(resp?.data?.siteById?.locales?.active ?? []).map(l => l.code)
if (!state.active.includes(state.primary)) {
state.active.push(state.primary)
}
@ -337,17 +316,10 @@ async function save () {
})
const resp = respRaw?.data?.localization?.updateLocale?.responseResult || {}
if (resp.succeeded) {
// Change UI language
this.$i18n.locale = state.selectedLocale
$q.notify({
type: 'positive',
message: 'Locale settings updated successfully.'
})
setTimeout(() => {
window.location.reload(true)
}, 1000)
} else {
$q.notify({
type: 'negative',

@ -40,8 +40,8 @@ export const useUserStore = defineStore('user', {
}
},
actions: {
isTokenValid () {
return this.exp && this.exp > DateTime.now()
isTokenValid (offset) {
return this.exp && this.exp > (offset ? DateTime.now().plus(offset) : DateTime.now())
},
loadToken () {
if (!this.token) { return }

Loading…
Cancel
Save