import { Model } from 'objection' import { find } from 'lodash-es' import { stat, readFile } from 'node:fs/promises' import path from 'node:path' import { DateTime } from 'luxon' /** * Locales model */ export class Locale extends Model { static get tableName() { return 'locales' } static get idColumn() { return 'code' } static get jsonSchema () { return { type: 'object', required: ['code', 'name'], properties: { code: {type: 'string'}, isRTL: {type: 'boolean', default: false}, name: {type: 'string'}, nativeName: {type: 'string'}, createdAt: {type: 'string'}, updatedAt: {type: 'string'}, completeness: {type: 'integer'} } } } static get jsonAttributes() { return ['strings'] } $beforeUpdate() { this.updatedAt = new Date().toISOString() } $beforeInsert() { this.createdAt = new Date().toISOString() this.updatedAt = new Date().toISOString() } static async refreshFromDisk ({ force = false } = {}) { try { const localesMeta = (await import(`../locales/metadata.mjs`)).default WIKI.logger.info(`Found ${localesMeta.languages.length} locales: [ OK ]`) const dbLocales = await WIKI.db.locales.query().select('code', 'updatedAt') let localFilesSkipped = 0 for (const lang of localesMeta.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('-') // -> Get DB version const dbLang = find(dbLocales, ['code', langFilename]) // -> Get File version const flPath = path.join(WIKI.SERVERPATH, `locales/${langFilename}.json`) try { const flStat = await stat(flPath) const flUpdatedAt = DateTime.fromJSDate(flStat.mtime) // -> Load strings if (!dbLang || DateTime.fromJSDate(dbLang.updatedAt) < flUpdatedAt || force) { WIKI.logger.debug(`Loading locale ${langFilename} into DB...`) const flStrings = JSON.parse(await readFile(flPath, 'utf8')) await WIKI.db.locales.query().insert({ code: langFilename, name: lang.name, nativeName: lang.localizedName, language: lang.language, region: lang.region, script: lang.script, isRTL: lang.isRtl, strings: flStrings }).onConflict('code').merge(['strings', 'updatedAt']) } else { WIKI.logger.debug(`Locale ${langFilename} is newer in the DB. Skipping disk version. [ OK ]`) } } catch (err) { localFilesSkipped++ WIKI.logger.debug(`Locale ${langFilename} not found on disk. Missing strings file. [ SKIPPED ]`) } } if (localFilesSkipped > 0) { WIKI.logger.info(`${localFilesSkipped} locales were defined in the metadata file but not found on disk. [ SKIPPED ]`) } } catch (err) { WIKI.logger.warn(`Failed to load locales from disk: [ FAILED ]`) WIKI.logger.warn(err) return false } } static async getLocales ({ cache = true } = {}) { if (!WIKI.cache.has('locales') || !cache) { const locales = await WIKI.db.locales.query().select('code', 'isRTL', 'language', 'name', 'nativeName', 'createdAt', 'updatedAt', 'completeness') WIKI.cache.set('locales', locales) for (const locale of locales) { WIKI.cache.set(`locale:${locale.code}`, locale) } } return WIKI.cache.get('locales') } static async getStrings (locale) { const { strings } = await WIKI.db.locales.query().findOne('code', locale).column('strings') return strings } static async reloadCache () { await WIKI.db.locales.getLocales({ cache: false }) } static async getNavLocales({ cache = false } = {}) { return [] // if (!WIKI.config.lang.namespacing) { // return [] // } // if (cache) { // const navLocalesCached = await WIKI.cache.get('nav:locales') // if (navLocalesCached) { // return navLocalesCached // } // } // const navLocales = await WIKI.db.locales.query().select('code', 'nativeName AS name').whereIn('code', WIKI.config.lang.namespaces).orderBy('code') // if (navLocales) { // if (cache) { // await WIKI.cache.set('nav:locales', navLocales, 300) // } // return navLocales // } else { // WIKI.logger.warn('Site Locales for navigation are missing or corrupted.') // return [] // } } }