Unverified Commit 57c9d37f authored by NGPixel's avatar NGPixel

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

parent 2587778e
...@@ -514,7 +514,9 @@ export async function up (knex) { ...@@ -514,7 +514,9 @@ export async function up (knex) {
{ {
key: 'update', key: 'update',
value: { value: {
locales: true lastCheckedAt: null,
version: null,
versionDate: null
} }
}, },
{ {
......
...@@ -356,6 +356,7 @@ ...@@ -356,6 +356,7 @@
"admin.instances.lastSeen": "Last Seen", "admin.instances.lastSeen": "Last Seen",
"admin.instances.subtitle": "View a list of active instances", "admin.instances.subtitle": "View a list of active instances",
"admin.instances.title": "Instances", "admin.instances.title": "Instances",
"admin.locale.active": "Active Locales",
"admin.locale.activeNamespaces": "Active Namespaces", "admin.locale.activeNamespaces": "Active Namespaces",
"admin.locale.autoUpdate.hint": "Automatically download updates to this locale as they become available.", "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.", "admin.locale.autoUpdate.hintWithNS": "Automatically download updates to all namespaced locales enabled below.",
...@@ -368,6 +369,8 @@ ...@@ -368,6 +369,8 @@
"admin.locale.download": "Download", "admin.locale.download": "Download",
"admin.locale.downloadNew": "Install New Locale", "admin.locale.downloadNew": "Install New Locale",
"admin.locale.downloadTitle": "Download 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.name": "Name",
"admin.locale.namespaces.hint": "Enables multiple language versions of the same page.", "admin.locale.namespaces.hint": "Enables multiple language versions of the same page.",
"admin.locale.namespaces.label": "Multilingual Namespaces", "admin.locale.namespaces.label": "Multilingual Namespaces",
...@@ -375,6 +378,8 @@ ...@@ -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.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.namespacingPrefixWarning.title": "The locale code will be prefixed to all paths. (e.g. /{langCode}/page-name)",
"admin.locale.nativeName": "Native 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.rtl": "RTL",
"admin.locale.settings": "Locale Settings", "admin.locale.settings": "Locale Settings",
"admin.locale.sideload": "Sideload Locale Package", "admin.locale.sideload": "Sideload Locale Package",
......
...@@ -2,7 +2,15 @@ export async function task (payload) { ...@@ -2,7 +2,15 @@ export async function task (payload) {
WIKI.logger.info('Checking for latest version...') WIKI.logger.info('Checking for latest version...')
try { 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 ]') WIKI.logger.info('Checked for latest version: [ COMPLETED ]')
} catch (err) { } catch (err) {
......
import { setTimeout } from 'node:timers/promises'
export async function task (payload) { export async function task (payload) {
if (WIKI.config.update?.locales === false) {
return
}
WIKI.logger.info('Fetching latest localization data...') WIKI.logger.info('Fetching latest localization data...')
try { 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 ]') WIKI.logger.info('Fetched latest localization data: [ COMPLETED ]')
} catch (err) { } catch (err) {
......
...@@ -29,7 +29,7 @@ export default boot(({ app }) => { ...@@ -29,7 +29,7 @@ export default boot(({ app }) => {
} }
// -> Refresh Token // -> Refresh Token
if (!userStore.isTokenValid()) { if (!userStore.isTokenValid({ minutes: 1 })) {
if (!fetching) { if (!fetching) {
refreshPromise = new Promise((resolve, reject) => { refreshPromise = new Promise((resolve, reject) => {
(async () => { (async () => {
......
...@@ -7,15 +7,6 @@ q-page.admin-locale ...@@ -7,15 +7,6 @@ q-page.admin-locale
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.locale.title') }} .text-h5.text-primary.animated.fadeInLeft {{ t('admin.locale.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.locale.subtitle') }} .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.locale.subtitle') }}
.col-auto.flex .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( q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle' icon='las la-question-circle'
flat flat
...@@ -55,73 +46,65 @@ q-page.admin-locale ...@@ -55,73 +46,65 @@ q-page.admin-locale
q-item q-item
blueprint-icon(icon='translation') blueprint-icon(icon='translation')
q-item-section q-item-section
q-item-label {{state.namespacing ? t(`admin.locale.base.labelWithNS`) : t(`admin.locale.base.label`)}} q-item-label {{t(`admin.locale.primary`)}}
q-item-label(caption) {{t(`admin.locale.base.hint`)}} q-item-label(caption) {{t(`admin.locale.primaryHint`)}}
q-item-section q-item-section
q-select( q-select(
outlined outlined
v-model='state.primary' v-model='state.primary'
:options='installedLocales' :options='state.locales'
option-value='code' option-value='code'
option-label='name' option-label='name'
emit-value emit-value
map-options map-options
dense dense
:aria-label='t(`admin.locale.base.label`)' :aria-label='t(`admin.locale.primary`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item(tag='label') q-item(tag='label')
blueprint-icon(icon='unit') blueprint-icon(icon='unit')
q-item-section q-item-section
q-item-label {{t(`admin.locale.namespaces.label`)}} q-item-label {{t(`admin.locale.forcePrefix`)}}
q-item-label(caption) {{t(`admin.locale.namespaces.hint`)}} q-item-label(caption) {{t(`admin.locale.forcePrefixHint`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='state.namespacing' v-model='state.forcePrefix'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' 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 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( q-item(
v-for='(lc, idx) of installedLocales' v-for='(lc, idx) of state.locales'
:key='lc.code' :key='lc.code'
:tag='lc.code !== state.selectedLocale ? `label` : null' :tag='lc.code !== state.selectedLocale ? `label` : null'
) )
blueprint-icon(:text='lc.code') blueprint-icon(:text='lc.language')
q-item-section q-item-section
q-item-label {{lc.name}} q-item-label {{lc.nativeName}}
q-item-label(caption) {{lc.nativeName}} q-item-label(caption) {{lc.name}} ({{lc.code}})
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
:disable='lc.code === state.selectedLocale' :disable='lc.code === state.primary'
v-model='state.namespaces' v-model='state.active'
:val='lc.code' :val='lc.code'
color='primary' :color='lc.code === state.primary ? `secondary` : `primary`'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='lc.name' :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%;') img(src='/_assets/illustrations/undraw_world.svg', style='width: 80%;')
//- q-separator.q-my-sm(inset) //- q-separator.q-my-sm(inset)
...@@ -150,7 +133,7 @@ q-page.admin-locale ...@@ -150,7 +133,7 @@ q-page.admin-locale
<script setup> <script setup>
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { cloneDeep, filter } from 'lodash-es' import { cloneDeep, sortBy } from 'lodash-es'
import LocaleInstallDialog from '../components/LocaleInstallDialog.vue' import LocaleInstallDialog from '../components/LocaleInstallDialog.vue'
...@@ -186,15 +169,10 @@ const state = reactive({ ...@@ -186,15 +169,10 @@ const state = reactive({
loading: 0, loading: 0,
locales: [], locales: [],
primary: 'en', primary: 'en',
forcePrefix: false,
active: [] active: []
}) })
// COMPUTED
const installedLocales = computed(() => {
return filter(state.locales, ['isInstalled', true])
})
// WATCHERS // WATCHERS
watch(() => adminStore.currentSiteId, (newValue) => { watch(() => adminStore.currentSiteId, (newValue) => {
...@@ -226,9 +204,8 @@ async function load () { ...@@ -226,9 +204,8 @@ async function load () {
completeness completeness
code code
createdAt createdAt
isInstalled
installDate
isRTL isRTL
language
name name
nativeName nativeName
region region
...@@ -241,7 +218,9 @@ async function load () { ...@@ -241,7 +218,9 @@ async function load () {
id id
locales { locales {
primary primary
active active {
code
}
} }
} }
} }
...@@ -251,9 +230,9 @@ async function load () { ...@@ -251,9 +230,9 @@ async function load () {
}, },
fetchPolicy: 'network-only' 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.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)) { if (!state.active.includes(state.primary)) {
state.active.push(state.primary) state.active.push(state.primary)
} }
...@@ -337,17 +316,10 @@ async function save () { ...@@ -337,17 +316,10 @@ async function save () {
}) })
const resp = respRaw?.data?.localization?.updateLocale?.responseResult || {} const resp = respRaw?.data?.localization?.updateLocale?.responseResult || {}
if (resp.succeeded) { if (resp.succeeded) {
// Change UI language
this.$i18n.locale = state.selectedLocale
$q.notify({ $q.notify({
type: 'positive', type: 'positive',
message: 'Locale settings updated successfully.' message: 'Locale settings updated successfully.'
}) })
setTimeout(() => {
window.location.reload(true)
}, 1000)
} else { } else {
$q.notify({ $q.notify({
type: 'negative', type: 'negative',
......
...@@ -40,8 +40,8 @@ export const useUserStore = defineStore('user', { ...@@ -40,8 +40,8 @@ export const useUserStore = defineStore('user', {
} }
}, },
actions: { actions: {
isTokenValid () { isTokenValid (offset) {
return this.exp && this.exp > DateTime.now() return this.exp && this.exp > (offset ? DateTime.now().plus(offset) : DateTime.now())
}, },
loadToken () { loadToken () {
if (!this.token) { return } if (!this.token) { return }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment