import { Client } from 'discord.js'; import { readdir } from 'fs/promises'; import { removeExtension } from './misc'; /** * 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>(); 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) // Ignore the default key .filter(str => str !== 'default') .map(str => { return [str, content[str]]; }), ) ); }) ); // Check locales sanity checkLocales(locales, default_lang); return locales; }; /** * Builds a dictionary, if a translation is not available, * we fallback to default lang. * @param client Client * @param text Name of string to fetch * @returns the dictionary */ export const getLocalizations = (client: Client, text: string, lowercase = false) => { const data: Record = {}; // 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 let str = locale.get(text) ?? client.locales.get(client.config.default_lang)?.get(text); // Store it if defined if (str !== undefined) { if (lowercase) { str = str.toLowerCase(); } data[lang] = str; } }); return data; }; /** * Return the locale data for a lang, * 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)); }); return locales; }; /** * Show percentage of translation progression. * * Raise an error if the default lang isn't * the lang with most text. * @param locales Locales loaded * @param default_lang default lang * @returns void */ const checkLocales = async (locales: Map>, default_lang: string) => { // Associate each lang with the number of locale it has let locales_size = new Map(); locales.forEach((locales_data, lang) => { locales_size.set(lang, locales_data.size); }); // Sort the map locales_size = new Map([...locales_size.entries()] .sort((a, b) => b[1] - a[1])); // Check if default lang is 100% const [max_size_name] = locales_size.keys(); 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}).` ); } // Remove the default language as it is used as a reference locales_size.delete(default_lang); // Displays the percentages according to the default language // lower is bigger const bar_size = 4; locales_size.forEach((size, lang) => { const percentage = (size / max_size) * 100; // Colored bar part const blocks = ' '.repeat(Math.floor(percentage / bar_size)); // Blank bar part const blank = ' '.repeat(Math.ceil((100 - percentage) / bar_size)); const color = () => { switch (true) { case percentage <= 25: // Red return '\x1b[41m'; case percentage <= 50: // Mangeta return '\x1b[45m'; case percentage <= 75: // Cyan return '\x1b[46m'; case percentage <= 100: // Green return '\x1b[42m'; default: return ''; } }; const padding = ' '.repeat(lang.length === 5 ? 1 : 4); console.log(`${padding}${lang} | ${color()}${blocks}\x1b[0m${blank} | ${percentage.toPrecision(3)}%`); }); };