diff --git a/.gitignore b/.gitignore index e9a53bf..2d53da6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ node_modules/**/* packages/ioc/node_modules/* packages/poster/dts/discord.d.ts +packages/**/node_modules diff --git a/packages/builder/package.json b/packages/builder/package.json index 8d4cda2..c7fed0e 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -18,8 +18,7 @@ "discord-api-types": "latest" }, "devDependencies": { - "@types/node": "^20.1.0", - "typescript": "^5.0.4" + "@types/node": "^20.1.0" }, "keywords": [], "author": "", diff --git a/packages/localizer/.gitignore b/packages/localizer/.gitignore new file mode 100644 index 0000000..3be4a7b --- /dev/null +++ b/packages/localizer/.gitignore @@ -0,0 +1,2 @@ +dist/ +*.tgz diff --git a/packages/localizer/index.mdx b/packages/localizer/index.mdx new file mode 100644 index 0000000..eda90fa --- /dev/null +++ b/packages/localizer/index.mdx @@ -0,0 +1,75 @@ +--- +title: Localizer +description: Translate your bot for the world +sidebar: + order: 1 +--- + + +# @sern/localizer + +A localization module for managing translations and providing localized content in your application. + +## Installation + +``` +npm i @sern/localizer +``` + +## Usage + +**Initializing the Localizer** +```ts +import { makeDependencies } from '@sern/handler'; +import { Localization } from '@sern/localizer'; + +await makeDependencies(({ add }) => { + add('localizer', Localization()); +}); +``` +This localizer is **FILE BASED**. +Create the directory `assets/locals`. Each json file in here must be named after the `locale` + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + + + + ```json title=~/assets/locals/es.json + { + "salute": { + "hello": "hola" + } + } + ``` + + + ```json title=~/assets/locals/en-US.json + { + "salute": { + "hello": "hello" + } + } + ``` + + + + + +**Accessing translations** +- If you are in a command execute callback, use `deps` from SDT. +```ts +execute : (ctx, { deps }) => { + //the localizer object from makeDependencies + deps.localizer + // Returns the Spanish translation for 'salute.hello' + deps.localizer.translate("salute.hello", "es"); +} +``` + + +```ts +import { local } from '@sern/localizer'; + +// Returns the Spanish translation for 'salute.hello' +const greeting = local('salute.hello', 'es'); +``` diff --git a/packages/localizer/index.ts b/packages/localizer/index.ts new file mode 100644 index 0000000..a926c7d --- /dev/null +++ b/packages/localizer/index.ts @@ -0,0 +1,145 @@ +import { type Init, Service, CommandInitPlugin, CommandType, controller } from '@sern/handler' +import { Localization as LocalsProvider } from 'shrimple-locales' +import fs from 'node:fs/promises' +import { existsSync } from 'node:fs' +import { join, resolve } from 'node:path'; +import assert from 'node:assert'; +import { dfsApplyLocalization } from './internal' + + +/** + * @since 3.4.0 + * @internal + */ +class ShrimpleLocalizer implements Init { + private __localization!: LocalsProvider; + private currentLocale: string = "en"; + + translationsFor(path: string): Record { + return this.__localization.localizationFor(path); + } + + translate(text: string, local?: string): string { + return this.__localization.get(text, local); + } + + setCurrentLocale(local: string): void { + this.__localization.changeLanguage(local); + } + + async init() { + const map = await this.readLocalizationDirectory(); + this.__localization = new LocalsProvider({ + defaultLocale: this.currentLocale, + fallbackLocale: "en", + locales: map + }); + } + + private async readLocalizationDirectory() { + const translationFiles = []; + const localPath = resolve('assets', 'locals'); + assert(existsSync(localPath), "No directory \"assets/locals\" found for the localizer") + for(const json_path of await fs.readdir(localPath)) { + const parsed = JSON.parse(await fs.readFile(join(localPath, json_path), 'utf8')) + const name = json_path.substring(0, json_path.lastIndexOf('.')); + translationFiles.push({ [name]: parsed }) + } + return translationFiles.reduce((acc, cur) => ({ ...cur, ...acc }), {}); + } +} + +/** + * Translates a string to its respective local + * Note: this method only works AFTER your container has been initiated + * @example + * ```ts + * assert.deepEqual(locals("salute.hello", "es"), "hola") + * ``` + */ +export const local = (i: string, local: string) => { + return Service('localizer').translate(i, local) +} + + + + +/** + * An init plugin to add localization fields to a command module. + * Your localization configuration should look like, + * @param root {string} If you have conflicting command names, you may configure the root of the name. (= command/{root}) + * Below is es.json (spanish) + * ```json + { + "command/comer" : { + "description": "Comer en Texas", + "options": { + "chicken": { + "name": "pollo", + "description": "un pollo largo" + } + } + } + } + ``` + */ +export const localize = (root?: string) => + //@ts-ignore + CommandInitPlugin(({ module, deps }) => { + if(module.type === CommandType.Slash || module.type === CommandType.Both) { + deps['@sern/logger'].info({ message: "Localizing "+ module.name }); + const resolvedLocalization= 'command/'+(root??module.name); + Reflect.set(module, 'name_localizations', deps.localizer.translationsFor(resolvedLocalization+".name")); + Reflect.set(module, 'description_localizations', deps.localizer.translationsFor(resolvedLocalization+'.description')); + const newOpts = module.options ?? []; + //@ts-ignore + dfsApplyLocalization(newOpts, deps, [resolvedLocalization]); + return controller.next(); + } else { + //@ts-ignore + return controller.stop("Cannot localize this type of module " + module.name); + } +}) + +export interface Localizer { + /** + * Returns an object containing translations for the given path. + * The object keys are the translation keys, and the values are the translated strings. + * + * @param path - The path to the translations file or directory. + * @returns An object with translation keys and their corresponding translated strings. + */ + translationsFor(path: string): Record; + + /** + * Translates the given text to the specified locale. + * + * @param text - The text to be translated. + * @param locale - The locale to translate the text to. + * @returns The translated text. + */ + translate(text: string, locale?: string): string; + + /** + * Sets the current locale to be used for translation. + * @param locale - The locale to set as the current locale. + */ + setCurrentLocale(locale: string): void; +} + +/** + * A service which provides simple file based localization. Add this while making dependencies. + * @example + * ```ts + * await makeDependencies(({ add }) => { + * add('localizer', Localization()); + * }); + * ``` + **/ +export const Localization = (defaultLocale?: string) => { + const localizer = new ShrimpleLocalizer; + if (defaultLocale) { + localizer.setCurrentLocale(defaultLocale); + } + return localizer as Localizer; +} diff --git a/packages/localizer/internal.ts b/packages/localizer/internal.ts new file mode 100644 index 0000000..c8bf6cd --- /dev/null +++ b/packages/localizer/internal.ts @@ -0,0 +1,50 @@ + +export interface Option { + name:string, + type: number, + options?: Array