Skip to content

Commit

Permalink
🌍 Proprietary system for translations implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashu11-A committed Jul 20, 2024
1 parent 18bf7d9 commit 864b5dd
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 105 deletions.
33 changes: 3 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 6 additions & 8 deletions src/class/crypt.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<string, string | object | boolean | number>()

export class Crypt {
async checker () {
Expand Down Expand Up @@ -127,7 +125,7 @@ export class Crypt {
if (await exists(join(rootPath, '..', '.hash'))) await rm(join(rootPath, '..', '.hash'))
}

async read (ephemeral?: boolean): Promise<DataCrypted | undefined> {
async read (ephemeral?: boolean, changeLang: boolean = true): Promise<DataCrypted | undefined> {
const token = await this.getToken()
if (token === undefined) return
const existKey = await exists(join(rootPath, '..', '.key'))
Expand All @@ -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]>) {
Expand All @@ -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()
Expand Down
6 changes: 3 additions & 3 deletions src/class/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ export class Page<PageGeneric extends PageTypes, Req = any, Loader = any> {

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)
}
}
}
2 changes: 1 addition & 1 deletion src/controller/cloudflare.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { credentials } from '@/class/crypt.js'
import { credentials } from '@/index.js'
import Cloudflare from 'cloudflare'

const createClient = async () => {
Expand Down
103 changes: 50 additions & 53 deletions src/controller/lang.ts
Original file line number Diff line number Diff line change
@@ -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<string, typeof langBase>()

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<P extends Paths<typeof langBase>>(path: P, metadata?: Record<string, string>): ValueOfLang<typeof langBase, P> | string {
const keys = path.split('.') as Array<string>
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<string, string>)[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<FsBackendOptions>({
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
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@ export const zone: Cache<{ name: string, id: string }> = new Cache<{ name: strin
*
* @type {Cache<Record[]>}
*/
export const records: Cache<Record[]> = new Cache<Record[]>('records')
export const records: Cache<Record[]> = new Cache<Record[]>('records')

export const credentials = new Map<string, string | object | boolean | number>()
4 changes: 2 additions & 2 deletions src/pages/dns/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<string, Properties>
const properties = JSON.parse(await readFile(`${join(rootPath, '..')}/locales/${Lang.language}/cloudflare.json`, { encoding: 'utf-8' }))[record] as Record<string, Properties>

// console.log(properties)

Expand Down
2 changes: 1 addition & 1 deletion src/pages/home.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down
27 changes: 24 additions & 3 deletions src/pages/home/language.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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
},
})
3 changes: 2 additions & 1 deletion src/pages/home/logout.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand Down
21 changes: 21 additions & 0 deletions src/types/lang.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export interface LangProps {
language: 'pt-BR' | 'en' | string
}

export type ExtractObjectPath<T, Key extends keyof T> = Key extends string
? T[Key] extends Record<string, any>
? `${Key}.${ExtractObjectPath<T[Key], Extract<keyof T[Key], string>>}`
: `${Key}`
: never

export type Paths<T> = ExtractObjectPath<T, keyof T>

export type ValueOfLang<T, P extends Paths<T>> = P extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? Rest extends Paths<T[Key]>
? ValueOfLang<T[Key], Rest>
: never
: never
: P extends keyof T
? T[P]
: never

0 comments on commit 864b5dd

Please sign in to comment.