chapterjs 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/build.js +9 -10
- package/dist/cli/dev.js +17 -16
- package/dist/cli/project.d.ts +38 -25
- package/dist/cli/project.js +199 -134
- package/dist/commands/command.d.ts +25 -11
- package/dist/commands/command.js +5 -5
- package/dist/commands/{convention.d.ts → declaration.d.ts} +24 -11
- package/dist/commands/{convention.js → declaration.js} +61 -33
- package/dist/commands/tree.d.ts +8 -4
- package/dist/commands/tree.js +16 -6
- package/dist/components/button.d.ts +13 -13
- package/dist/components/button.js +9 -11
- package/dist/components/component.d.ts +1 -1
- package/dist/components/component.js +1 -1
- package/dist/components/custom-id.d.ts +4 -4
- package/dist/components/custom-id.js +12 -12
- package/dist/components/declaration.d.ts +42 -0
- package/dist/components/{convention.js → declaration.js} +76 -73
- package/dist/components/declared.d.ts +29 -0
- package/dist/components/declared.js +49 -0
- package/dist/components/embed.d.ts +13 -12
- package/dist/components/embed.js +6 -6
- package/dist/components/instance.d.ts +3 -3
- package/dist/components/instance.js +2 -2
- package/dist/components/modal.d.ts +11 -10
- package/dist/components/modal.js +7 -8
- package/dist/components/router.d.ts +3 -2
- package/dist/components/router.js +14 -19
- package/dist/components/select.d.ts +14 -12
- package/dist/components/select.js +4 -4
- package/dist/events/declaration.d.ts +16 -0
- package/dist/events/declaration.js +80 -0
- package/dist/events/event.d.ts +42 -5
- package/dist/events/event.js +25 -11
- package/dist/events/registry.d.ts +1 -10
- package/dist/events/router.d.ts +3 -2
- package/dist/index.d.ts +11 -9
- package/dist/index.js +2 -0
- package/dist/loader/generated.d.ts +5 -32
- package/dist/loader/generated.js +21 -55
- package/dist/loader/loader.d.ts +57 -44
- package/dist/loader/loader.js +73 -89
- package/dist/main.d.ts +0 -1
- package/dist/main.js +2 -3
- package/dist/messages/commands.d.ts +3 -3
- package/dist/messages/commands.js +29 -10
- package/dist/messages/declaration.d.ts +89 -0
- package/dist/messages/declaration.js +455 -0
- package/dist/messages/messages.d.ts +65 -43
- package/dist/messages/messages.js +11 -10
- package/dist/messages/phrases.js +1 -1
- package/dist/messages/translate.d.ts +4 -4
- package/dist/messages/translate.js +4 -4
- package/dist/presence/declaration.d.ts +19 -0
- package/dist/presence/{convention.js → declaration.js} +13 -28
- package/dist/presence/presence.d.ts +7 -7
- package/dist/presence/presence.js +5 -5
- package/dist/structures/context.d.ts +1 -1
- package/dist/structures/interaction.d.ts +2 -2
- package/dist/structures/interaction.js +3 -3
- package/dist/structures/payload.d.ts +4 -4
- package/dist/structures/payload.js +4 -4
- package/dist/tasks/declaration.d.ts +16 -0
- package/dist/tasks/{convention.js → declaration.js} +14 -36
- package/dist/tasks/scheduler.d.ts +3 -1
- package/dist/tasks/scheduler.js +4 -4
- package/dist/tasks/task.d.ts +8 -9
- package/dist/tasks/task.js +6 -7
- package/package.json +1 -1
- package/dist/components/convention.d.ts +0 -35
- package/dist/components/file.d.ts +0 -30
- package/dist/components/file.js +0 -52
- package/dist/events/convention.d.ts +0 -25
- package/dist/events/convention.js +0 -97
- package/dist/events/types.d.ts +0 -7
- package/dist/events/types.js +0 -48
- package/dist/messages/convention.d.ts +0 -89
- package/dist/messages/convention.js +0 -347
- package/dist/presence/convention.d.ts +0 -26
- package/dist/tasks/convention.d.ts +0 -26
package/dist/loader/loader.d.ts
CHANGED
|
@@ -1,72 +1,85 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
* The folder, inside `src/`. With `single`, the name of the one file
|
|
8
|
-
* instead: `'presence'` is `src/presence.ts`.
|
|
9
|
-
*/
|
|
10
|
-
folder: string;
|
|
1
|
+
/** Where a declaration was found: a file of the project, and its export. */
|
|
2
|
+
export interface ExportSite {
|
|
3
|
+
/** The path from the project folder, with `/`: `src/moderation/ban.ts`. */
|
|
4
|
+
file: string;
|
|
5
|
+
/** The name of the export: `'ban'`, or `'default'`. */
|
|
6
|
+
export: string;
|
|
11
7
|
/**
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
8
|
+
* The name the export gives to what it declares: the name of the export,
|
|
9
|
+
* or the name of the file (without extension) for the default export.
|
|
14
10
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
/** A kind of declaration a feature reads from the exports of the files. */
|
|
14
|
+
export interface Declaration<T> {
|
|
15
|
+
/** What one is called in messages: "event", "command"... */
|
|
17
16
|
one: string;
|
|
18
17
|
many: string;
|
|
18
|
+
/** Whether a value is one of these, before it is read. */
|
|
19
|
+
is(value: unknown): boolean;
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* Whether a file may export several in a list. Only what names itself
|
|
22
|
+
* (an event, a command, a language) may: a list has no export name to
|
|
23
|
+
* give each one.
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
|
+
list: boolean;
|
|
25
26
|
/**
|
|
26
|
-
* Checks
|
|
27
|
-
* Throws an error whose message says what
|
|
27
|
+
* Checks a declaration and returns what the feature needs from it.
|
|
28
|
+
* Throws an error whose message says what to write.
|
|
28
29
|
*/
|
|
29
|
-
read(
|
|
30
|
+
read(value: unknown, site: ExportSite): T;
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
file: string;
|
|
32
|
+
/** A declaration, read, with where it comes from. */
|
|
33
|
+
export interface LoadedItem<T> extends ExportSite {
|
|
34
34
|
value: T;
|
|
35
35
|
}
|
|
36
|
+
/** An export the framework could not use, or a file that could not run. */
|
|
36
37
|
export interface FailedFile {
|
|
37
38
|
file: string;
|
|
39
|
+
/** The export that failed, when the file itself ran. */
|
|
40
|
+
export?: string;
|
|
38
41
|
error: unknown;
|
|
39
42
|
}
|
|
43
|
+
/** The declarations of one kind found in the files. */
|
|
40
44
|
export interface LoadResult<T> {
|
|
41
|
-
loaded:
|
|
45
|
+
loaded: LoadedItem<T>[];
|
|
42
46
|
failed: FailedFile[];
|
|
43
|
-
/** Every file found, loaded or not. */
|
|
44
|
-
files: string[];
|
|
45
47
|
}
|
|
46
|
-
/**
|
|
47
|
-
export declare
|
|
48
|
-
|
|
48
|
+
/** What `file` is called in a message: `src/a.ts`, or `src/a.ts (ban)`. */
|
|
49
|
+
export declare const siteName: (site: {
|
|
50
|
+
file: string;
|
|
51
|
+
export?: string;
|
|
52
|
+
}) => string;
|
|
53
|
+
/** A file of the project, found but not run. */
|
|
49
54
|
export interface FoundFile {
|
|
50
|
-
/** The path from the project folder, with `/`: `src/
|
|
55
|
+
/** The path from the project folder, with `/`: `src/ping.ts`. */
|
|
51
56
|
file: string;
|
|
52
57
|
/** Where it is on disk. */
|
|
53
58
|
path: string;
|
|
54
59
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
/** Loads every file of a conventional folder. A broken file never throws. */
|
|
61
|
-
export declare function loadFolder<T>(projectDir: string, convention: Convention<T>): Promise<LoadResult<T>>;
|
|
62
|
-
/** A file of the project as a build kept it: already run. */
|
|
63
|
-
export interface BuiltFile {
|
|
64
|
-
/** The path it had in the project: `src/events/ping.ts`. */
|
|
60
|
+
/** Every source file of `src/`, in a stable order. */
|
|
61
|
+
export declare function listSources(projectDir: string): Promise<FoundFile[]>;
|
|
62
|
+
/** A file of the project, run: what it exports. */
|
|
63
|
+
export interface LoadedModule {
|
|
64
|
+
/** The path from the project folder: `src/ping.ts`. */
|
|
65
65
|
file: string;
|
|
66
66
|
exports: Record<string, unknown>;
|
|
67
67
|
}
|
|
68
|
+
/** The files of the project, run (`modules`) or not (`failed`). */
|
|
69
|
+
export interface LoadedSources {
|
|
70
|
+
modules: LoadedModule[];
|
|
71
|
+
failed: FailedFile[];
|
|
72
|
+
}
|
|
73
|
+
/** Runs every file of `src/`. A file that fails to run never throws. */
|
|
74
|
+
export declare function loadSources(projectDir: string): Promise<LoadedSources>;
|
|
68
75
|
/**
|
|
69
|
-
* Reads the
|
|
70
|
-
*
|
|
76
|
+
* Reads the declarations of one kind from what the files export. Every
|
|
77
|
+
* export is looked at; the ones the kind recognises are read, the others
|
|
78
|
+
* are someone else's (another kind, or plain code). The same value
|
|
79
|
+
* exported twice (re-exported by another file) is one declaration, at the
|
|
80
|
+
* first place it is found. An export that can't be read is returned in
|
|
81
|
+
* `failed`, with its name, and never throws.
|
|
71
82
|
*/
|
|
72
|
-
export declare function
|
|
83
|
+
export declare function readDeclarations<T>(modules: readonly LoadedModule[], declaration: Declaration<T>): LoadResult<T>;
|
|
84
|
+
/** A file of the project as a build kept it: already run. */
|
|
85
|
+
export type BuiltFile = LoadedModule;
|
package/dist/loader/loader.js
CHANGED
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
// Loads the files
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Loads the files of a project and finds what they declare. Every source
|
|
2
|
+
// file under `src/` is loaded, wherever it is and whatever it is called:
|
|
3
|
+
// what the framework runs is what the files export, made with one of its
|
|
4
|
+
// functions (`command()`, `event()`, `button()`...). One mechanism for
|
|
5
|
+
// every feature: a feature only says how to recognise and read its
|
|
6
|
+
// declarations.
|
|
4
7
|
import { readdir } from 'node:fs/promises';
|
|
5
8
|
import { join, relative, sep } from 'node:path';
|
|
6
9
|
import { importFile } from './hot.js';
|
|
10
|
+
/** What `file` is called in a message: `src/a.ts`, or `src/a.ts (ban)`. */
|
|
11
|
+
export const siteName = (site) => site.export === undefined || site.export === 'default'
|
|
12
|
+
? site.file
|
|
13
|
+
: `${site.file} (${site.export})`;
|
|
7
14
|
const SOURCE = /\.(?:ts|mts|js|mjs)$/;
|
|
8
15
|
const DECLARATION = /\.d\.m?ts$/;
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
* the same in every feature:
|
|
12
|
-
* - a file or a folder whose name starts with `_` is private: never
|
|
13
|
-
* loaded by the framework, free to hold shared code;
|
|
14
|
-
* - a folder whose name is in parentheses, like `(moderation)`, only groups
|
|
15
|
-
* files: it is not part of what the path says, at any depth.
|
|
16
|
-
*/
|
|
17
|
-
const PRIVATE = /^_/;
|
|
18
|
-
const GROUP = /^\(.*\)$/;
|
|
19
|
-
/** The path of a file as the feature reads it: without `(group)` folders. */
|
|
20
|
-
export function logicalPath(inside) {
|
|
21
|
-
const parts = inside.split('/');
|
|
22
|
-
return parts
|
|
23
|
-
.filter((part, index) => index === parts.length - 1 || !GROUP.test(part))
|
|
24
|
-
.join('/');
|
|
25
|
-
}
|
|
16
|
+
/** Tests next to the code: they are for a test runner, not for the bot. */
|
|
17
|
+
const TEST = /\.(?:test|spec)\.[^.]+$/;
|
|
26
18
|
/** The source files of a folder and its subfolders, in a stable order. */
|
|
27
19
|
async function scan(dir) {
|
|
28
20
|
let entries;
|
|
@@ -30,57 +22,26 @@ async function scan(dir) {
|
|
|
30
22
|
entries = await readdir(dir, { withFileTypes: true });
|
|
31
23
|
}
|
|
32
24
|
catch (error) {
|
|
33
|
-
// A folder the project does not have is a feature it does not use.
|
|
34
25
|
if (error.code === 'ENOENT')
|
|
35
26
|
return [];
|
|
36
27
|
throw error;
|
|
37
28
|
}
|
|
38
29
|
const files = [];
|
|
39
30
|
for (const entry of entries.sort((a, b) => (a.name < b.name ? -1 : 1))) {
|
|
40
|
-
if (PRIVATE.test(entry.name))
|
|
41
|
-
continue;
|
|
42
31
|
const path = join(dir, entry.name);
|
|
43
32
|
if (entry.isDirectory())
|
|
44
33
|
files.push(...(await scan(path)));
|
|
45
|
-
else if (SOURCE.test(entry.name) &&
|
|
34
|
+
else if (SOURCE.test(entry.name) &&
|
|
35
|
+
!DECLARATION.test(entry.name) &&
|
|
36
|
+
!TEST.test(entry.name)) {
|
|
46
37
|
files.push(path);
|
|
47
38
|
}
|
|
48
39
|
}
|
|
49
40
|
return files;
|
|
50
41
|
}
|
|
51
|
-
/**
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
SOURCE.test(file) &&
|
|
55
|
-
!DECLARATION.test(file) &&
|
|
56
|
-
!file.slice(`src/${folder}.`.length).includes('/'));
|
|
57
|
-
}
|
|
58
|
-
/** The one file of a single-file convention, with any source extension. */
|
|
59
|
-
async function scanSingle(dir, name) {
|
|
60
|
-
let entries;
|
|
61
|
-
try {
|
|
62
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
if (error.code === 'ENOENT')
|
|
66
|
-
return [];
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
return entries
|
|
70
|
-
.filter(entry => entry.isFile() &&
|
|
71
|
-
entry.name.startsWith(`${name}.`) &&
|
|
72
|
-
SOURCE.test(entry.name) &&
|
|
73
|
-
!DECLARATION.test(entry.name))
|
|
74
|
-
.map(entry => join(dir, entry.name));
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* The files of a conventional folder, in a stable order (or the one file
|
|
78
|
-
* of a single-file convention, under every source extension it has).
|
|
79
|
-
*/
|
|
80
|
-
export async function listFolder(projectDir, convention) {
|
|
81
|
-
const paths = convention.single
|
|
82
|
-
? await scanSingle(join(projectDir, 'src'), convention.folder)
|
|
83
|
-
: await scan(join(projectDir, 'src', convention.folder));
|
|
42
|
+
/** Every source file of `src/`, in a stable order. */
|
|
43
|
+
export async function listSources(projectDir) {
|
|
44
|
+
const paths = await scan(join(projectDir, 'src'));
|
|
84
45
|
return paths
|
|
85
46
|
.map(path => ({
|
|
86
47
|
file: relative(projectDir, path).split(sep).join('/'),
|
|
@@ -88,48 +49,71 @@ export async function listFolder(projectDir, convention) {
|
|
|
88
49
|
}))
|
|
89
50
|
.sort((a, b) => (a.file < b.file ? -1 : 1));
|
|
90
51
|
}
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
async function readFiles(convention, files, exportsOf) {
|
|
97
|
-
const prefix = convention.single ? 'src/' : `src/${convention.folder}/`;
|
|
98
|
-
const result = { loaded: [], failed: [], files: [...files] };
|
|
99
|
-
await Promise.all(files.map(async (file) => {
|
|
52
|
+
/** Runs every file of `src/`. A file that fails to run never throws. */
|
|
53
|
+
export async function loadSources(projectDir) {
|
|
54
|
+
const found = await listSources(projectDir);
|
|
55
|
+
const result = { modules: [], failed: [] };
|
|
56
|
+
await Promise.all(found.map(async ({ file, path }) => {
|
|
100
57
|
try {
|
|
101
|
-
|
|
102
|
-
// A misplaced file is not run at all.
|
|
103
|
-
convention.check?.(inside);
|
|
104
|
-
const value = convention.read(await exportsOf(file), inside);
|
|
105
|
-
result.loaded.push({ file, value });
|
|
58
|
+
result.modules.push({ file, exports: await importFile(path) });
|
|
106
59
|
}
|
|
107
60
|
catch (error) {
|
|
108
61
|
result.failed.push({ file, error });
|
|
109
62
|
}
|
|
110
63
|
}));
|
|
111
64
|
const byFile = (a, b) => a.file < b.file ? -1 : 1;
|
|
112
|
-
result.
|
|
65
|
+
result.modules.sort(byFile);
|
|
113
66
|
result.failed.sort(byFile);
|
|
114
|
-
result.files.sort();
|
|
115
67
|
return result;
|
|
116
68
|
}
|
|
117
|
-
/**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
69
|
+
/** The name of a file, without its folders and its extension. */
|
|
70
|
+
const baseName = (file) => file
|
|
71
|
+
.split('/')
|
|
72
|
+
.pop()
|
|
73
|
+
.replace(/\.[^.]+$/, '');
|
|
123
74
|
/**
|
|
124
|
-
* Reads the
|
|
125
|
-
*
|
|
75
|
+
* Reads the declarations of one kind from what the files export. Every
|
|
76
|
+
* export is looked at; the ones the kind recognises are read, the others
|
|
77
|
+
* are someone else's (another kind, or plain code). The same value
|
|
78
|
+
* exported twice (re-exported by another file) is one declaration, at the
|
|
79
|
+
* first place it is found. An export that can't be read is returned in
|
|
80
|
+
* `failed`, with its name, and never throws.
|
|
126
81
|
*/
|
|
127
|
-
export function
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
.
|
|
134
|
-
|
|
82
|
+
export function readDeclarations(modules, declaration) {
|
|
83
|
+
const result = { loaded: [], failed: [] };
|
|
84
|
+
const seen = new Set();
|
|
85
|
+
const sites = [...modules]
|
|
86
|
+
.sort((a, b) => (a.file < b.file ? -1 : 1))
|
|
87
|
+
.flatMap(({ file, exports }) => Object.keys(exports)
|
|
88
|
+
.sort()
|
|
89
|
+
.map(name => ({ file, export: name, value: exports[name] })));
|
|
90
|
+
for (const { file, export: exported, value } of sites) {
|
|
91
|
+
const site = {
|
|
92
|
+
file,
|
|
93
|
+
export: exported,
|
|
94
|
+
name: exported === 'default' ? baseName(file) : exported,
|
|
95
|
+
};
|
|
96
|
+
const read = (one) => {
|
|
97
|
+
if (seen.has(one))
|
|
98
|
+
return;
|
|
99
|
+
seen.add(one);
|
|
100
|
+
result.loaded.push({ ...site, value: declaration.read(one, site) });
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
if (declaration.is(value))
|
|
104
|
+
read(value);
|
|
105
|
+
else if (Array.isArray(value) && value.some(declaration.is)) {
|
|
106
|
+
if (!declaration.list) {
|
|
107
|
+
throw new TypeError(`This export is a list with a ${declaration.one} in it: export each ${declaration.one} on its own (export const ${site.name} = ...), its name is the name of its export.`);
|
|
108
|
+
}
|
|
109
|
+
for (const one of value)
|
|
110
|
+
if (declaration.is(one))
|
|
111
|
+
read(one);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
result.failed.push({ file, export: exported, error });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
135
119
|
}
|
package/dist/main.d.ts
CHANGED
package/dist/main.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// What `import ... from 'chapterjs'` loads at runtime: the public API, plus
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// `asset()`, whose type depends on the project it is imported from. Its
|
|
3
|
+
// type is not here but in the `.chapterjs/` folder of each project.
|
|
4
4
|
export * from './index.js';
|
|
5
|
-
export { event } from './events/event.js';
|
|
6
5
|
export { asset } from './assets/asset.js';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { type LoadedCommand } from '../commands/
|
|
1
|
+
import { type LoadedCommand } from '../commands/declaration.js';
|
|
2
2
|
import type { FailedFile } from '../loader/loader.js';
|
|
3
3
|
import type { LoadedMessages } from './translate.js';
|
|
4
|
-
/** A command given its texts by the
|
|
4
|
+
/** A command given its texts by the language files. */
|
|
5
5
|
export interface TranslatedCommand {
|
|
6
6
|
/** The command, described; `null` when it can't be, said in `failed`. */
|
|
7
7
|
readonly command: LoadedCommand | null;
|
|
8
8
|
/** What is wrong, on the file it is wrong in. */
|
|
9
9
|
readonly failed: readonly FailedFile[];
|
|
10
10
|
}
|
|
11
|
-
/** The
|
|
11
|
+
/** The names the languages translate that no command has, by file. */
|
|
12
12
|
export declare function unknownCommands(messages: LoadedMessages, known: ReadonlySet<string>): {
|
|
13
13
|
file: string;
|
|
14
14
|
path: string;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// The `commands` of the
|
|
2
|
-
//
|
|
1
|
+
// The `commands` of the language files: what Discord shows of each command
|
|
2
|
+
// in each language. A command has its texts in one place: in
|
|
3
3
|
// its file (`description`, `locales`), or in the language files, where the
|
|
4
4
|
// default language gives what Discord shows to everyone and the others
|
|
5
5
|
// their translations. Which one is decided by `description` in the file.
|
|
6
|
-
import { checkLocales, } from '../commands/
|
|
6
|
+
import { checkLocales, } from '../commands/declaration.js';
|
|
7
7
|
import { commandName } from '../commands/tree.js';
|
|
8
|
-
/** The
|
|
8
|
+
/** The names the languages translate that no command has, by file. */
|
|
9
9
|
export function unknownCommands(messages, known) {
|
|
10
10
|
const unknown = [];
|
|
11
11
|
for (const [locale, section] of messages.commands) {
|
|
@@ -18,14 +18,14 @@ export function unknownCommands(messages, known) {
|
|
|
18
18
|
return unknown;
|
|
19
19
|
}
|
|
20
20
|
/** Where the default language file is, or would be. */
|
|
21
|
-
export const defaultLanguageFile = (messages) => messages?.files.get(messages.default) ?? '
|
|
21
|
+
export const defaultLanguageFile = (messages) => messages?.files.get(messages.default) ?? 'the default language file';
|
|
22
22
|
/**
|
|
23
23
|
* Gives a command its texts from the languages, when its file does not
|
|
24
24
|
* describe it; refuses the languages that describe a command its file
|
|
25
25
|
* already does. What is wrong is reported on the file it is wrong in.
|
|
26
26
|
*/
|
|
27
27
|
export function translateCommand(file, command, messages) {
|
|
28
|
-
const path = command.path.join('
|
|
28
|
+
const path = command.path.join(' ');
|
|
29
29
|
const name = commandName(command.path);
|
|
30
30
|
const failed = [];
|
|
31
31
|
const languages = [...(messages?.commands ?? [])].filter(([, section]) => path in section);
|
|
@@ -53,8 +53,25 @@ export function translateCommand(file, command, messages) {
|
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
// The default language describes the command; when it does not, the
|
|
57
|
+
// first language that does (en-US first) is what Discord shows to all.
|
|
58
|
+
const order = messages
|
|
59
|
+
? [...given.keys()].sort((a, b) => a === messages.default
|
|
60
|
+
? -1
|
|
61
|
+
: b === messages.default
|
|
62
|
+
? 1
|
|
63
|
+
: a === 'en-US'
|
|
64
|
+
? -1
|
|
65
|
+
: b === 'en-US'
|
|
66
|
+
? 1
|
|
67
|
+
: a < b
|
|
68
|
+
? -1
|
|
69
|
+
: 1)
|
|
70
|
+
: [];
|
|
71
|
+
const mainLocale = order.find(locale => given.get(locale).descriptions[locale]) ??
|
|
72
|
+
messages?.default;
|
|
73
|
+
const main = mainLocale ? given.get(mainLocale) : undefined;
|
|
74
|
+
const description = mainLocale ? main?.descriptions[mainLocale] : undefined;
|
|
58
75
|
if (description === undefined) {
|
|
59
76
|
failed.push({
|
|
60
77
|
file,
|
|
@@ -65,7 +82,9 @@ export function translateCommand(file, command, messages) {
|
|
|
65
82
|
const options = [];
|
|
66
83
|
for (const option of command.options) {
|
|
67
84
|
const text = option.description ||
|
|
68
|
-
|
|
85
|
+
(mainLocale
|
|
86
|
+
? main?.optionLocales[option.name]?.descriptions[mainLocale]
|
|
87
|
+
: undefined);
|
|
69
88
|
if (!text) {
|
|
70
89
|
failed.push({
|
|
71
90
|
file,
|
|
@@ -81,7 +100,7 @@ export function translateCommand(file, command, messages) {
|
|
|
81
100
|
const descriptions = {};
|
|
82
101
|
const optionLocales = {};
|
|
83
102
|
for (const [locale, texts] of given) {
|
|
84
|
-
if (locale ===
|
|
103
|
+
if (locale === mainLocale)
|
|
85
104
|
continue;
|
|
86
105
|
const text = texts.names[locale];
|
|
87
106
|
if (text !== undefined)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Locale } from '../discord/types/common.js';
|
|
2
|
+
import { type Declaration, type ExportSite, type FailedFile } from '../loader/loader.js';
|
|
3
|
+
import { type LoadedMessages, type Template } from './translate.js';
|
|
4
|
+
/** A language, checked: what the languages are assembled from. */
|
|
5
|
+
export interface LoadedLanguage {
|
|
6
|
+
/** The language. */
|
|
7
|
+
readonly locale: Locale;
|
|
8
|
+
/** Whether it says it is the default language. */
|
|
9
|
+
readonly isDefault: boolean;
|
|
10
|
+
/** The texts, compiled. */
|
|
11
|
+
readonly texts: ReadonlyMap<string, Template>;
|
|
12
|
+
/** The phrases of the framework this file translates, compiled. */
|
|
13
|
+
readonly framework: ReadonlyMap<string, Template>;
|
|
14
|
+
/** The `commands` section, by the name of the command, not checked yet. */
|
|
15
|
+
readonly commands: Readonly<Record<string, unknown>> | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A language, declared anywhere in `src/` with `language()`: `locale` says
|
|
19
|
+
* which one, `texts` what the bot says in it.
|
|
20
|
+
*/
|
|
21
|
+
export declare const languageDeclaration: Declaration<LoadedLanguage>;
|
|
22
|
+
/** A language as the project loaded it, with where it is declared. */
|
|
23
|
+
export interface LanguageEntry extends ExportSite {
|
|
24
|
+
readonly language: LoadedLanguage;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Puts the languages together: one default, which `t` is typed from and
|
|
28
|
+
* gives a text to the languages that do not have it, and every other
|
|
29
|
+
* language with texts the default has, the same placeholders included. A
|
|
30
|
+
* language that does not match is left out and reported on its file: the
|
|
31
|
+
* others still work. `null` when there is no language at all.
|
|
32
|
+
*/
|
|
33
|
+
export declare function assembleMessages(entries: readonly LanguageEntry[]): {
|
|
34
|
+
messages: LoadedMessages | null;
|
|
35
|
+
failed: FailedFile[];
|
|
36
|
+
};
|
|
37
|
+
/** A language, where it is declared. */
|
|
38
|
+
export interface DeclaredLanguage extends ExportSite {
|
|
39
|
+
readonly locale: Locale;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The augmentation that types `t` from the files themselves: the editor
|
|
43
|
+
* reads every declaration of the default language directly, so the keys
|
|
44
|
+
* and placeholders follow every keystroke; the other languages take the
|
|
45
|
+
* texts they do not have from it. `languages` is every language declared,
|
|
46
|
+
* `defaultLocale` the one `t` is typed from (the first one when it is not
|
|
47
|
+
* known yet: `sync`, which runs nothing). Nothing when the project has no
|
|
48
|
+
* language.
|
|
49
|
+
*/
|
|
50
|
+
export declare function messagesDeclarations(languages: readonly DeclaredLanguage[], defaultLocale?: Locale): string;
|
|
51
|
+
/**
|
|
52
|
+
* The augmentation that underlines a second default language in the
|
|
53
|
+
* editor: when more than one language says `default: true` (`defaults`,
|
|
54
|
+
* the ones that do, known once the files ran), `language()` refuses
|
|
55
|
+
* `default: true` for each of them, so they are both underlined and not
|
|
56
|
+
* only reported in the console. Nothing when at most one says it.
|
|
57
|
+
*/
|
|
58
|
+
export declare function languageDefaultsDeclaration(defaults: readonly Locale[]): string;
|
|
59
|
+
/**
|
|
60
|
+
* The languages of a project, found without running its files: a text
|
|
61
|
+
* search for `language({ locale: '...' })` in the exports of each file.
|
|
62
|
+
* Approximate on purpose, for `sync`, which runs nothing: `dev` and
|
|
63
|
+
* `build` then write the exact ones. Returns the languages with the one
|
|
64
|
+
* that says `default: true`, when one does.
|
|
65
|
+
*/
|
|
66
|
+
export declare function scanLanguages(cwd: string): Promise<{
|
|
67
|
+
languages: DeclaredLanguage[];
|
|
68
|
+
defaultLocale?: Locale;
|
|
69
|
+
}>;
|
|
70
|
+
/** A command, and whether it has its own `description`. */
|
|
71
|
+
export interface DeclaredCommand {
|
|
72
|
+
/** `'ping'`, `'mod ban'` */
|
|
73
|
+
readonly name: string;
|
|
74
|
+
/**
|
|
75
|
+
* Whether the command gives its own texts; `false` when it is not
|
|
76
|
+
* known, like before the files ran: the language files may then
|
|
77
|
+
* describe it.
|
|
78
|
+
*/
|
|
79
|
+
readonly described: boolean;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The augmentation that offers the commands of the project in the
|
|
83
|
+
* `commands` of a language: the name of each command, `true` when the
|
|
84
|
+
* language files describe it and `false` when the command has its own
|
|
85
|
+
* `description`, so that the editor only offers the first ones. Both come
|
|
86
|
+
* from running the files (`dev` and `build` know them, `sync` does not and
|
|
87
|
+
* offers any name). Nothing when the project has no command.
|
|
88
|
+
*/
|
|
89
|
+
export declare function commandsDeclarations(commands: readonly DeclaredCommand[]): string;
|