Botanique/src/utils/locales.ts

170 lines
4.7 KiB
TypeScript

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<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)
// 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) => {
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;
}
});
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<string, Map<string, string>>, default_lang: string) => {
// Associate each lang with the number of locale it has
let locales_size = new Map<string, number>();
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(percentage / bar_size);
// Blank bar part
const blank = ' '.repeat((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 '';
}
};
console.log(`${lang} | ${color()}${blocks}\x1b[0m${blank} | ${percentage}%`);
});
};