From 864b5dd4ab234376a7a106103df71726802ec2e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=A7=81Ash=C3=BB=EA=A7=82?= <30575805+Ashu11-A@users.noreply.github.com> Date: Sat, 20 Jul 2024 02:12:20 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=8D=20Proprietary=20system=20for=20tra?= =?UTF-8?q?nslations=20implemented?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 33 +---------- package.json | 2 - src/class/crypt.ts | 14 ++--- src/class/pages.ts | 6 +- src/controller/cloudflare.ts | 2 +- src/controller/lang.ts | 103 +++++++++++++++++------------------ src/index.ts | 4 +- src/pages/dns/create.ts | 4 +- src/pages/home.ts | 2 +- src/pages/home/language.ts | 27 ++++++++- src/pages/home/logout.ts | 3 +- src/types/lang.ts | 21 +++++++ 12 files changed, 116 insertions(+), 105 deletions(-) create mode 100644 src/types/lang.ts diff --git a/package-lock.json b/package-lock.json index 72154ad..78b1edd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,6 @@ "enmap": "^6.0.2", "enquirer": "^2.4.1", "glob": "^11.0.0", - "i18next": "^23.11.5", - "i18next-fs-backend": "^2.3.1", "inquirer-autocomplete-standalone": "^0.8.1", "node-forge": "^1.3.1", "ora": "^8.0.1" @@ -1908,6 +1906,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -6776,33 +6775,6 @@ "ms": "^2.0.0" } }, - "node_modules/i18next": { - "version": "23.11.5", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.5.tgz", - "integrity": "sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==", - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], - "dependencies": { - "@babel/runtime": "^7.23.2" - } - }, - "node_modules/i18next-fs-backend": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.3.1.tgz", - "integrity": "sha512-tvfXskmG/9o+TJ5Fxu54sSO5OkY6d+uMn+K6JiUGLJrwxAVfer+8V3nU8jq3ts9Pe5lXJv4b1N7foIjJ8Iy2Gg==" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -9405,7 +9377,8 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true }, "node_modules/regenerator-transform": { "version": "0.15.2", diff --git a/package.json b/package.json index 179de6d..921130a 100644 --- a/package.json +++ b/package.json @@ -81,8 +81,6 @@ "enmap": "^6.0.2", "enquirer": "^2.4.1", "glob": "^11.0.0", - "i18next": "^23.11.5", - "i18next-fs-backend": "^2.3.1", "inquirer-autocomplete-standalone": "^0.8.1", "node-forge": "^1.3.1", "ora": "^8.0.1" diff --git a/src/class/crypt.ts b/src/class/crypt.ts index be76310..9bab81e 100644 --- a/src/class/crypt.ts +++ b/src/class/crypt.ts @@ -1,8 +1,9 @@ -import { i18, Lang } from '@/controller/lang.js' -import { rootPath } from '@/index.js' +import { i18, lang } from '@/controller/lang.js' +import { credentials, rootPath } from '@/index.js' import { exists } from '@/lib/exists.js' import { isJson } from '@/lib/validate.js' import { DataCrypted } from '@/types/crypt.js' +import { QuestionTypes } from '@/types/questions.js' import * as argon2 from 'argon2' import { passwordStrength } from 'check-password-strength' import { watch } from 'chokidar' @@ -12,9 +13,6 @@ import { readFile, rm, writeFile } from 'fs/promises' import forge from 'node-forge' import { join } from 'path' import { question } from './questions.js' -import { QuestionTypes } from '@/types/questions.js' - -export const credentials = new Map() export class Crypt { async checker () { @@ -127,7 +125,7 @@ export class Crypt { if (await exists(join(rootPath, '..', '.hash'))) await rm(join(rootPath, '..', '.hash')) } - async read (ephemeral?: boolean): Promise { + async read (ephemeral?: boolean, changeLang: boolean = true): Promise { const token = await this.getToken() if (token === undefined) return const existKey = await exists(join(rootPath, '..', '.key')) @@ -143,7 +141,7 @@ export class Crypt { const outputData = JSON.parse(AESCrypt) as DataCrypted - if (outputData.language !== undefined) new Lang().setLanguage(outputData.language) + if (outputData.language !== undefined && changeLang) lang.setLanguage(outputData.language) for (const [key, value] of Object.entries(outputData) as Array<[string, string | object | boolean | number]>) { @@ -164,7 +162,7 @@ export class Crypt { const token = await this.getToken() if (token === undefined) return - const data = Object.assign(await this.read(true) ?? {}, value) + const data = Object.assign(await this.read(true, false) ?? {}, value) const AESCrypt = CryptoJS.AES.encrypt(JSON.stringify(data), token).toString() const BlowfishCrypt = CryptoJS.Blowfish.encrypt(AESCrypt, token).toString() diff --git a/src/class/pages.ts b/src/class/pages.ts index 0b8bbb5..cfc71e0 100755 --- a/src/class/pages.ts +++ b/src/class/pages.ts @@ -95,13 +95,13 @@ export class Page { switch (page.interaction.type) { case PageTypes.Option: - await page.interaction.run(page) + page.interaction.run(page) break case PageTypes.Command: - await page.interaction.run(page) + page.interaction.run(page) break case PageTypes.SubCommand: - await page.interaction.run(page) + page.interaction.run(page) } } } diff --git a/src/controller/cloudflare.ts b/src/controller/cloudflare.ts index 2cf9758..3a1d89b 100755 --- a/src/controller/cloudflare.ts +++ b/src/controller/cloudflare.ts @@ -1,4 +1,4 @@ -import { credentials } from '@/class/crypt.js' +import { credentials } from '@/index.js' import Cloudflare from 'cloudflare' const createClient = async () => { diff --git a/src/controller/lang.ts b/src/controller/lang.ts index 4244c6c..6828e06 100644 --- a/src/controller/lang.ts +++ b/src/controller/lang.ts @@ -1,65 +1,62 @@ import { Crypt } from '@/class/crypt.js' -import { question } from '@/class/questions.js' -import { rootPath } from '@/index.js' +import { credentials, rootPath } from '@/index.js' import { exists } from '@/lib/exists.js' -import { QuestionTypes } from '@/types/questions.js' -import flags from 'country-code-to-flag-emoji' -import { glob } from 'glob' -import i18next, { TFunction } from 'i18next' -import Backend, { FsBackendOptions } from 'i18next-fs-backend' +import { LangProps, Paths, ValueOfLang } from '@/types/lang.js' +import { readFile } from 'fs/promises' import { join } from 'path' +import langBase from '../../locales/en/translation.json' +import { glob } from 'glob' + +const cache = new Map() + +const path = join(rootPath, '..', 'locales') +const allLangs = (await glob('**/translation.json', { cwd: path })) + +for (const lang of allLangs) { + cache.set(lang.split('/')[0], JSON.parse(await readFile(join(path, lang), { encoding: 'utf-8' }))) +} + +export class Lang { + static language: string + + constructor ({ language }: LangProps) { + Lang.language = language + } + + get

>(path: P, metadata?: Record): ValueOfLang | string { + const keys = path.split('.') as Array + let result: string | undefined | object + const data = cache.get(Lang.language) as any | undefined + + if (data === undefined) throw new Error('Cache not defined fotr languages') -export class Lang { - async setLanguage (lang: string, change?: boolean) { - const path = join(rootPath, '..', 'locales', lang) + for (const key of keys) { + if (result === undefined) { result = data[key]; continue } + if (typeof result === 'object') { result = (result as Record)[key]; continue } + } + + if (metadata !== undefined){ + for (const [key, data] of Object.entries(metadata)) { + (result as string).replaceAll(key, data) + } + } + + return result as string + } + + async setLanguage (newLang: string, change?: boolean) { + const path = join(rootPath, '..', 'locales', newLang) const crypt = new Crypt() if (!(await exists(path))) { - console.log(`⛔ The selected language (${lang}) does not exist, using English by default`) - await i18next.changeLanguage('en') - if (change) await crypt.write({ language: 'en' }) - } else { - await i18next.changeLanguage(lang) - if (change) await crypt.write({ language: lang }) - } - } - - async selectLanguage () { - const path = join(rootPath, '..', 'locales') - const allLangs = (await glob('**/*.json', { cwd: path })).map((lang) => lang.split('/')[0]) - const langs = [] - for (const lang of allLangs) { - if (langs.filter((langExist) => langExist === lang).length == 0) langs.push(lang) + console.log(`⛔ The selected language (${newLang}) does not exist, using English by default`) + newLang = 'en' } - const choices = langs.map((lang) => ({ name: `${flags(lang)} - ${lang}`, value: lang })) - const response = await question({ - type: QuestionTypes.Select, - message: 'Which language should I continue with?', - choices - }) - if (response === undefined) throw new Error('Please select a language') - this.setLanguage(response, true) - } - /** - * Inicializar i18 - */ - async create () { - return await i18next.use(Backend).init({ - debug: true, - initImmediate: false, - lng: i18next.language ?? 'en', - fallbackLng: 'en', - backend: { - loadPath: join(rootPath, '..', 'locales', '{{lng}}', '{{ns}}.json'), - } - }) + Lang.language = newLang + if (change) await crypt.write({ language: newLang }) } } -/** - * Package language controller - * - * @type {TFunction<'translation', undefined>} - */ -export const i18: TFunction<'translation', undefined> = i18next.isInitialized ? i18next.t : await new Lang().create() +export const lang = new Lang({ language: credentials.get('language') as string ?? 'en' }) +export const i18 = lang.get \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 217cec1..cab9264 100755 --- a/src/index.ts +++ b/src/index.ts @@ -37,4 +37,6 @@ export const zone: Cache<{ name: string, id: string }> = new Cache<{ name: strin * * @type {Cache} */ -export const records: Cache = new Cache('records') \ No newline at end of file +export const records: Cache = new Cache('records') + +export const credentials = new Map() \ No newline at end of file diff --git a/src/pages/dns/create.ts b/src/pages/dns/create.ts index 2dd64cf..d555951 100755 --- a/src/pages/dns/create.ts +++ b/src/pages/dns/create.ts @@ -8,7 +8,7 @@ import { join } from 'path' import { client } from '@/controller/cloudflare.js' import { QuestionTypes } from '@/types/questions.js' import { readFile } from 'fs/promises' -import i18next from 'i18next' +import { Lang } from '@/controller/lang.js' type Properties = { type: string, @@ -66,7 +66,7 @@ new Page({ return options } - const properties = JSON.parse(await readFile(`${join(rootPath, '..')}/locales/${i18next.language}/cloudflare.json`, { encoding: 'utf-8' }))[record] as Record + const properties = JSON.parse(await readFile(`${join(rootPath, '..')}/locales/${Lang.language}/cloudflare.json`, { encoding: 'utf-8' }))[record] as Record // console.log(properties) diff --git a/src/pages/home.ts b/src/pages/home.ts index abc57f7..075fdb7 100644 --- a/src/pages/home.ts +++ b/src/pages/home.ts @@ -1,7 +1,7 @@ -import { credentials } from '@/class/crypt.js' import { Page } from '@/class/pages.js' import { question } from '@/class/questions.js' import { i18 } from '@/controller/lang.js' +import { credentials } from '@/index.js' import { PageTypes } from '@/types/page.js' import { Separator } from '@inquirer/prompts' diff --git a/src/pages/home/language.ts b/src/pages/home/language.ts index 9c75259..fc56921 100644 --- a/src/pages/home/language.ts +++ b/src/pages/home/language.ts @@ -1,6 +1,13 @@ import { Page } from '@/class/pages.js' -import { Lang } from '@/controller/lang.js' +import { question } from '@/class/questions.js' +import { lang } from '@/controller/lang.js' +import { rootPath } from '@/index.js' import { PageTypes } from '@/types/page.js' +import { join } from 'path' +import flags from 'country-code-to-flag-emoji' +import { QuestionTypes } from '@/types/questions.js' +import { glob } from 'glob' + new Page({ name: 'language', @@ -10,9 +17,23 @@ new Page({ next: 'home', previous: 'home', async run(options) { - await new Lang().selectLanguage() + const langs = [] + const path = join(rootPath, '..', 'locales') + const allLangs = (await glob('**/translation.json', { cwd: path })) + + for (const lang of allLangs.map((lang) => lang.split('/')[0])) { + if (langs.filter((langExist) => langExist === lang).length == 0) langs.push(lang) + } + const choices = langs.map((lang) => ({ name: `${flags(lang)} - ${lang}`, value: lang })) + const response = await question({ + type: QuestionTypes.Select, + message: 'Which language should I continue with?', + choices + }) + if (response === undefined) throw new Error('Please select a language') + await lang.setLanguage(response, true) - options.reply(options.interaction.name as string) + options.reply(options.interaction.next as string) return options }, }) \ No newline at end of file diff --git a/src/pages/home/logout.ts b/src/pages/home/logout.ts index 142912b..444e020 100644 --- a/src/pages/home/logout.ts +++ b/src/pages/home/logout.ts @@ -1,5 +1,6 @@ -import { credentials, Crypt } from '@/class/crypt.js' +import { Crypt } from '@/class/crypt.js' import { Page } from '@/class/pages.js' +import { credentials } from '@/index.js' import { PageTypes } from '@/types/page.js' new Page({ diff --git a/src/types/lang.ts b/src/types/lang.ts new file mode 100644 index 0000000..65a22d8 --- /dev/null +++ b/src/types/lang.ts @@ -0,0 +1,21 @@ +export interface LangProps { + language: 'pt-BR' | 'en' | string +} + +export type ExtractObjectPath = Key extends string + ? T[Key] extends Record + ? `${Key}.${ExtractObjectPath>}` + : `${Key}` + : never + +export type Paths = ExtractObjectPath + +export type ValueOfLang> = P extends `${infer Key}.${infer Rest}` + ? Key extends keyof T + ? Rest extends Paths + ? ValueOfLang + : never + : never + : P extends keyof T + ? T[P] + : never \ No newline at end of file