-
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathregistry.ts
More file actions
87 lines (74 loc) · 2.82 KB
/
registry.ts
File metadata and controls
87 lines (74 loc) · 2.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { Backend } from '@skyra/i18next-backend';
import { Locale, type LocaleString } from 'discord-api-types/v10';
import i18next, { getFixedT, type DefaultNamespace, type InitOptions, type Namespace, type TFunction } from 'i18next';
import type { PathLike } from 'node:fs';
import { opendir } from 'node:fs/promises';
import { join } from 'node:path';
i18next.use(Backend);
export const supportedLanguages = new Set(Object.values(Locale)) as ReadonlySet<LocaleString>;
export function isSupportedDiscordLocale(language: string): language is LocaleString {
return supportedLanguages.has(language as LocaleString);
}
export const loadedLocales = new Set<LocaleString>();
export const loadedNamespaces = new Set<string>();
export const loadedPaths = new Set<string>();
export const loadedFormatters: Formatter[] = [];
export interface Formatter {
name: string;
format: (value: any, lng: string | undefined, options: any) => string;
}
export function addFormatters(...formatters: readonly Formatter[]): void {
loadedFormatters.push(...formatters);
}
export async function init(options?: InitOptions) {
await i18next.init({
backend: { paths: [...loadedPaths] },
ns: [...loadedNamespaces],
preload: [...loadedLocales],
initImmediate: false,
ignoreJSONStructure: false,
...options,
interpolation: {
escapeValue: false,
skipOnVariables: false,
...options?.interpolation
}
});
for (const { name, format } of loadedFormatters) {
// @ts-expect-error TS says services doesn't exist, but it does
i18next.services.formatter!.add(name, format);
}
}
export async function load(directory: PathLike) {
const dir = await opendir(directory);
for await (const entry of dir) {
// If the entry is not a directory, skip:
if (!entry.isDirectory()) continue;
// If the locale is not supported by Discord, emit a warning and skip:
if (!isSupportedDiscordLocale(entry.name)) {
process.emitWarning('Unsupported Discord locale', {
code: 'UNSUPPORTED_LOCALE',
detail: `'${entry.name}' is not assignable to type LocaleString`
});
continue;
}
// Load the directory:
loadedLocales.add(entry.name);
await loadLocale(join(dir.path, entry.name), '');
}
loadedPaths.add(join(dir.path, '{{lng}}', '{{ns}}.json'));
}
async function loadLocale(directory: string, ns: string) {
const dir = await opendir(directory);
for await (const entry of dir) {
if (entry.isDirectory()) {
await loadLocale(join(dir.path, entry.name), `${ns}${entry.name}/`);
} else if (entry.isFile() && entry.name.endsWith('.json')) {
loadedNamespaces.add(`${ns}${entry.name.slice(0, -5)}`);
}
}
}
export function getT<const Ns extends Namespace = DefaultNamespace>(locale: LocaleString, namespace?: Ns): TFunction<Ns> {
if (!loadedLocales.has(locale)) throw new ReferenceError(`Invalid language (${locale})`);
return getFixedT<Ns>(locale, namespace);
}