feat: locales #27
7 changed files with 61 additions and 31 deletions
|
@ -3,7 +3,7 @@ import { Routes } from 'discord-api-types/v9';
|
|||
import { Client } from 'discord.js';
|
||||
import { readdir } from 'fs/promises';
|
||||
|
||||
/** Load all the commands */
|
||||
/** Load all the commands. */
|
||||
export default async (client: Client) => {
|
||||
const rest = new REST({ version: '9' }).setToken(client.token ?? '');
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ export default {
|
|||
data: (client: Client) => {
|
||||
const filename = getFilename(__filename);
|
||||
return new SlashCommandBuilder()
|
||||
.setName(filename)
|
||||
.setName(filename.toLowerCase())
|
||||
.setDescription(client.locales.get(client.config.default_lang)?.get(`c_${filename}_desc`) ?? '?')
|
||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`))
|
||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Client } from 'discord.js';
|
||||
import { readdir } from 'fs/promises';
|
||||
|
||||
/** Load all the events */
|
||||
/** Load all the events. */
|
||||
export default async (client: Client) => {
|
||||
const events_categories = (await readdir(__dirname))
|
||||
.filter(element => !element.endsWith('.js') && !element.endsWith('.ts'));
|
||||
|
|
|
@ -4,7 +4,7 @@ import loadCommands from './commands/loader';
|
|||
|
||||
import { logStart } from './utils/misc';
|
||||
|
||||
/** Run the bot */
|
||||
/** Run the bot. */
|
||||
const run = async () => {
|
||||
console.log('Starting Botanique...');
|
||||
|
||||
|
|
|
@ -3,15 +3,16 @@ import { readFileSync } from 'fs';
|
|||
import { SlashCommandBuilder } from '@discordjs/builders';
|
||||
import { loadLocales } from './locales';
|
||||
|
||||
const { version } = JSON.parse(readFileSync('./package.json').toString());
|
||||
|
||||
declare module 'discord.js' {
|
||||
// eslint-disable-next-line no-shadow
|
||||
export interface Client {
|
||||
/** Store the configuration */
|
||||
config: {
|
||||
/** Bot version */
|
||||
version: string,
|
||||
/** Bot token from env variable */
|
||||
token_discord: string | undefined,
|
||||
/** Default lang used */
|
||||
default_lang: string
|
||||
},
|
||||
/** Store all the slash commands */
|
||||
|
@ -27,7 +28,7 @@ declare module 'discord.js' {
|
|||
}
|
||||
}
|
||||
|
||||
/** Creation of the client and definition of its properties */
|
||||
/** Creation of the client and definition of its properties. */
|
||||
export default async () => {
|
||||
const client: Client = new Client({
|
||||
intents: [
|
||||
|
@ -36,14 +37,14 @@ export default async () => {
|
|||
});
|
||||
|
||||
client.config = {
|
||||
version: version,
|
||||
version: JSON.parse(readFileSync('./package.json').toString()).version,
|
||||
token_discord: process.env.TOKEN_DISCORD,
|
||||
default_lang: 'en-US',
|
||||
};
|
||||
|
||||
client.commands = new Collection();
|
||||
|
||||
console.log('Localizations progression :');
|
||||
console.log('Translations progression :');
|
||||
client.locales = await loadLocales(client.config.default_lang);
|
||||
|
||||
return client;
|
||||
|
|
|
@ -3,33 +3,38 @@ import { readdir } from 'fs/promises';
|
|||
import { removeExtension } from './misc';
|
||||
|
||||
/**
|
||||
* Load the localizations files into memory
|
||||
* Load the localizations files into memory.
|
||||
*
|
||||
* Show percentage of translations.
|
||||
* @param default_lang default lang
|
||||
* @returns Map of map with all the localizations
|
||||
*/
|
||||
export const loadLocales = async (default_lang: string) => {
|
||||
// Get files from locales/ directory
|
||||
const old_path = __dirname.split('/');
|
||||
old_path.pop();
|
||||
|
||||
const files = await readdir(`${old_path.join('/')}/locales`);
|
||||
|
||||
// Read JSON files content and load it into the memory
|
||||
const locales = new Map<string, Map<string, string>>();
|
||||
await Promise.all(
|
||||
files.map(async lang => {
|
||||
// Import file
|
||||
const content: {
|
||||
[key: string]: string
|
||||
} = await import(
|
||||
`../locales/${lang}`
|
||||
);
|
||||
|
||||
// Add it to the memory
|
||||
locales.set(
|
||||
removeExtension(lang),
|
||||
new Map(
|
||||
Object.keys(content)
|
||||
.filter(str => str !== 'default')
|
||||
.map(str => {
|
||||
return [str, content[str]];
|
||||
}),
|
||||
new Map(Object.keys(content)
|
||||
// Ignore the default key
|
||||
.filter(str => str !== 'default')
|
||||
.map(str => {
|
||||
return [str, content[str]];
|
||||
}),
|
||||
)
|
||||
);
|
||||
})
|
||||
|
@ -43,7 +48,7 @@ export const loadLocales = async (default_lang: string) => {
|
|||
|
||||
/**
|
||||
* Builds a dictionary, if a translation is not available,
|
||||
* we fallback to en-US.
|
||||
* we fallback to default lang.
|
||||
* @param client Client
|
||||
* @param text Name of string to fetch
|
||||
* @returns the dictionary
|
||||
|
@ -51,12 +56,16 @@ export const loadLocales = async (default_lang: string) => {
|
|||
export const getLocalizations = (client: Client, text: string) => {
|
||||
const data: Record<string, string> = {};
|
||||
|
||||
// Load all the localizations
|
||||
client.locales.forEach((locale, lang) => {
|
||||
// Fetch the text and fallback to default lang if needed
|
||||
// See getLocale for more info on why we *can* fallback
|
||||
const str = locale.get(text)
|
||||
?? client.locales.get(client.config.default_lang)?.get(text);
|
||||
|
||||
// Store it if defined
|
||||
if (str !== undefined) {
|
||||
data[lang] = str.toLowerCase();
|
||||
data[lang] = str;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -65,15 +74,22 @@ export const getLocalizations = (client: Client, text: string) => {
|
|||
|
||||
/**
|
||||
* Return the locale data for a lang,
|
||||
* fallback to default language when a string isn't available
|
||||
* fallback to default language when a string isn't available.
|
||||
* @param client Client
|
||||
* @param lang Lang to fetch
|
||||
* @returns the map with the desired languaged clogged with the default one
|
||||
*/
|
||||
export const getLocale = (client: Client, lang: string) => {
|
||||
// Load default lang
|
||||
const default_locales = client.locales.get(client.config.default_lang);
|
||||
// Load desired lang
|
||||
const desired_locales = client.locales.get(lang);
|
||||
|
||||
// Get text and fallback to default lang if needed
|
||||
//
|
||||
// We can fallback to the default one without any problem
|
||||
// because we make sure that the default language always contains
|
||||
// the desired text, and that the other languages are only translations
|
||||
const locales = new Map();
|
||||
default_locales?.forEach((_, key) => {
|
||||
locales.set(key, desired_locales?.get(key) ?? default_locales.get(key));
|
||||
|
@ -83,15 +99,15 @@ export const getLocale = (client: Client, lang: string) => {
|
|||
};
|
||||
|
||||
/**
|
||||
* Show percentage of translation progression
|
||||
* Show percentage of translation progression.
|
||||
*
|
||||
* Raise an error if the default lang isn't
|
||||
* the lang with most text
|
||||
* the lang with most text.
|
||||
* @param locales Locales loaded
|
||||
* @param default_lang default lang
|
||||
* @returns void
|
||||
*/
|
||||
export const checkLocales =
|
||||
const checkLocales =
|
||||
async (locales: Map<string, Map<string, string>>, default_lang: string) => {
|
||||
// Associate each lang with the number of locale it has
|
||||
let locales_size = new Map<string, number>();
|
||||
|
@ -108,6 +124,10 @@ async (locales: Map<string, Map<string, string>>, default_lang: string) => {
|
|||
const [max_size] = locales_size.values();
|
||||
const default_lang_size = locales_size.get(default_lang) ?? 0;
|
||||
if (max_size > default_lang_size) {
|
||||
// Throw error because in this case we are sure than the security
|
||||
// explained in getLocale isn't true.
|
||||
// However, it is possible that this condition is true
|
||||
// and the security is poor, but it's better than nothing.
|
||||
throw new Error(
|
||||
`The default locale (${default_lang} = ${default_lang_size}) isn't complete `
|
||||
+ `(${max_size_name} = ${max_size}).`
|
||||
|
@ -122,26 +142,29 @@ async (locales: Map<string, Map<string, string>>, default_lang: string) => {
|
|||
const bar_size = 4;
|
||||
locales_size.forEach((size, lang) => {
|
||||
const percentage = (size / max_size) * 100;
|
||||
// Colored bar part
|
||||
const blocks = ' '.repeat(percentage / bar_size);
|
||||
// Blank bar part
|
||||
const blank = ' '.repeat((100 - percentage) / bar_size);
|
||||
const color = () => {
|
||||
switch (true) {
|
||||
case percentage <= 25:
|
||||
// red
|
||||
// Red
|
||||
return '\x1b[41m';
|
||||
case percentage <= 50:
|
||||
// mangeta
|
||||
// Mangeta
|
||||
return '\x1b[45m';
|
||||
case percentage <= 75:
|
||||
// cyan
|
||||
// Cyan
|
||||
return '\x1b[46m';
|
||||
case percentage <= 100:
|
||||
// green
|
||||
// Green
|
||||
return '\x1b[42m';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
console.log(`${lang} | ${color()}${blocks}\x1b[0m${blank} | ${percentage}%`);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Log module status
|
||||
* Log module status.
|
||||
* @param {string} name Module name
|
||||
* @param {boolean} status Module status
|
||||
* @returns String
|
||||
|
@ -9,18 +9,24 @@ export const logStart = (name: string, status: boolean) => {
|
|||
};
|
||||
|
||||
/**
|
||||
* Filename without path and extension
|
||||
* Filename without path and extension.
|
||||
* @param path __filename
|
||||
* @returns string
|
||||
*/
|
||||
export const getFilename = (path: string) => {
|
||||
const path_list = path.split('/');
|
||||
|
||||
return path_list[path_list.length - 1].split('.')[0];
|
||||
// Check if filename exist
|
||||
const filename_with_ext = path_list.pop();
|
||||
if (filename_with_ext === undefined) {
|
||||
throw new Error(`Filename error: don't exist in ${path}`);
|
||||
}
|
||||
|
||||
return removeExtension(filename_with_ext);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove extension from a filename
|
||||
* Remove extension from a filename.
|
||||
* @param filename string of the filename with an extension
|
||||
* @returns string of the filename without an extension
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue