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.
Files changed (80) hide show
  1. package/dist/cli/build.js +9 -10
  2. package/dist/cli/dev.js +17 -16
  3. package/dist/cli/project.d.ts +38 -25
  4. package/dist/cli/project.js +199 -134
  5. package/dist/commands/command.d.ts +25 -11
  6. package/dist/commands/command.js +5 -5
  7. package/dist/commands/{convention.d.ts → declaration.d.ts} +24 -11
  8. package/dist/commands/{convention.js → declaration.js} +61 -33
  9. package/dist/commands/tree.d.ts +8 -4
  10. package/dist/commands/tree.js +16 -6
  11. package/dist/components/button.d.ts +13 -13
  12. package/dist/components/button.js +9 -11
  13. package/dist/components/component.d.ts +1 -1
  14. package/dist/components/component.js +1 -1
  15. package/dist/components/custom-id.d.ts +4 -4
  16. package/dist/components/custom-id.js +12 -12
  17. package/dist/components/declaration.d.ts +42 -0
  18. package/dist/components/{convention.js → declaration.js} +76 -73
  19. package/dist/components/declared.d.ts +29 -0
  20. package/dist/components/declared.js +49 -0
  21. package/dist/components/embed.d.ts +13 -12
  22. package/dist/components/embed.js +6 -6
  23. package/dist/components/instance.d.ts +3 -3
  24. package/dist/components/instance.js +2 -2
  25. package/dist/components/modal.d.ts +11 -10
  26. package/dist/components/modal.js +7 -8
  27. package/dist/components/router.d.ts +3 -2
  28. package/dist/components/router.js +14 -19
  29. package/dist/components/select.d.ts +14 -12
  30. package/dist/components/select.js +4 -4
  31. package/dist/events/declaration.d.ts +16 -0
  32. package/dist/events/declaration.js +80 -0
  33. package/dist/events/event.d.ts +42 -5
  34. package/dist/events/event.js +25 -11
  35. package/dist/events/registry.d.ts +1 -10
  36. package/dist/events/router.d.ts +3 -2
  37. package/dist/index.d.ts +11 -9
  38. package/dist/index.js +2 -0
  39. package/dist/loader/generated.d.ts +5 -32
  40. package/dist/loader/generated.js +21 -55
  41. package/dist/loader/loader.d.ts +57 -44
  42. package/dist/loader/loader.js +73 -89
  43. package/dist/main.d.ts +0 -1
  44. package/dist/main.js +2 -3
  45. package/dist/messages/commands.d.ts +3 -3
  46. package/dist/messages/commands.js +29 -10
  47. package/dist/messages/declaration.d.ts +89 -0
  48. package/dist/messages/declaration.js +455 -0
  49. package/dist/messages/messages.d.ts +65 -43
  50. package/dist/messages/messages.js +11 -10
  51. package/dist/messages/phrases.js +1 -1
  52. package/dist/messages/translate.d.ts +4 -4
  53. package/dist/messages/translate.js +4 -4
  54. package/dist/presence/declaration.d.ts +19 -0
  55. package/dist/presence/{convention.js → declaration.js} +13 -28
  56. package/dist/presence/presence.d.ts +7 -7
  57. package/dist/presence/presence.js +5 -5
  58. package/dist/structures/context.d.ts +1 -1
  59. package/dist/structures/interaction.d.ts +2 -2
  60. package/dist/structures/interaction.js +3 -3
  61. package/dist/structures/payload.d.ts +4 -4
  62. package/dist/structures/payload.js +4 -4
  63. package/dist/tasks/declaration.d.ts +16 -0
  64. package/dist/tasks/{convention.js → declaration.js} +14 -36
  65. package/dist/tasks/scheduler.d.ts +3 -1
  66. package/dist/tasks/scheduler.js +4 -4
  67. package/dist/tasks/task.d.ts +8 -9
  68. package/dist/tasks/task.js +6 -7
  69. package/package.json +1 -1
  70. package/dist/components/convention.d.ts +0 -35
  71. package/dist/components/file.d.ts +0 -30
  72. package/dist/components/file.js +0 -52
  73. package/dist/events/convention.d.ts +0 -25
  74. package/dist/events/convention.js +0 -97
  75. package/dist/events/types.d.ts +0 -7
  76. package/dist/events/types.js +0 -48
  77. package/dist/messages/convention.d.ts +0 -89
  78. package/dist/messages/convention.js +0 -347
  79. package/dist/presence/convention.d.ts +0 -26
  80. package/dist/tasks/convention.d.ts +0 -26
@@ -1,72 +1,85 @@
1
- /**
2
- * A folder of the project whose files the framework loads, or one file at
3
- * the root of `src/`.
4
- */
5
- export interface Convention<T> {
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 convention is one file, `src/<folder>.ts`, not a folder. `check`
13
- * and `read` receive its name with its extension (`presence.ts`).
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
- single?: boolean;
16
- /** What one file is called in messages: "event", "command"... */
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
- * Checks where a file is, before it is run. `path` is its path inside
21
- * the folder, with `/`, without the `(group)` folders (see below). Throws
22
- * an error whose message says where the file should be.
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
- check?(path: string): void;
25
+ list: boolean;
25
26
  /**
26
- * Checks what a file exports and returns what the feature needs from it.
27
- * Throws an error whose message says what the file should export.
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(exports: Record<string, unknown>, path: string): T;
30
+ read(value: unknown, site: ExportSite): T;
30
31
  }
31
- export interface LoadedFile<T> {
32
- /** The path from the project folder, with `/`: `src/events/ping.ts`. */
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: LoadedFile<T>[];
45
+ loaded: LoadedItem<T>[];
42
46
  failed: FailedFile[];
43
- /** Every file found, loaded or not. */
44
- files: string[];
45
47
  }
46
- /** The path of a file as the feature reads it: without `(group)` folders. */
47
- export declare function logicalPath(inside: string): string;
48
- /** A file of a conventional folder, found but not run. */
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/events/ping.ts`. */
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
- * The files of a conventional folder, in a stable order (or the one file
57
- * of a single-file convention, under every source extension it has).
58
- */
59
- export declare function listFolder(projectDir: string, convention: Pick<Convention<unknown>, 'folder' | 'single'>): Promise<FoundFile[]>;
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 files of a conventional folder from a build, which ran them
70
- * all at once: nothing is scanned and nothing is imported.
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 loadBuilt<T>(convention: Convention<T>, built: readonly BuiltFile[]): Promise<LoadResult<T>>;
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;
@@ -1,28 +1,20 @@
1
- // Loads the files users drop in the conventional folders of their project.
2
- // One mechanism for every folder: a feature only says which folder it owns
3
- // and how to read what a file exports.
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
- * How users organise a conventional folder without changing what it means,
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) && !DECLARATION.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
- /** Whether a path of the project is the file of a single-file convention. */
52
- function isSingle(file, folder) {
53
- return (file.startsWith(`src/${folder}.`) &&
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
- * Hands what files export to the convention of their folder. `exportsOf`
93
- * gives what a file exports (by running it, or from a build that already
94
- * did); a file that fails, there or in the convention, never throws.
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
- const inside = logicalPath(file.slice(prefix.length));
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.loaded.sort(byFile);
65
+ result.modules.sort(byFile);
113
66
  result.failed.sort(byFile);
114
- result.files.sort();
115
67
  return result;
116
68
  }
117
- /** Loads every file of a conventional folder. A broken file never throws. */
118
- export async function loadFolder(projectDir, convention) {
119
- const found = await listFolder(projectDir, convention);
120
- const paths = new Map(found.map(({ file, path }) => [file, path]));
121
- return readFiles(convention, found.map(({ file }) => file), file => importFile(paths.get(file)));
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 files of a conventional folder from a build, which ran them
125
- * all at once: nothing is scanned and nothing is imported.
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 loadBuilt(convention, built) {
128
- const prefix = `src/${convention.folder}/`;
129
- const mine = new Map(built
130
- .filter(({ file }) => convention.single
131
- ? isSingle(file, convention.folder)
132
- : file.startsWith(prefix))
133
- .map(({ file, exports }) => [file, exports]));
134
- return readFiles(convention, [...mine.keys()], file => Promise.resolve(mine.get(file)));
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
@@ -1,3 +1,2 @@
1
1
  export * from './index.js';
2
- export { event } from './events/event.js';
3
2
  export { asset } from './assets/asset.js';
package/dist/main.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // What `import ... from 'chapterjs'` loads at runtime: the public API, plus
2
- // the functions whose type depends on where they are imported from. Their
3
- // types are not here but in the `.chapterjs/` folder of each project.
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/convention.js';
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 languages of `src/messages/`. */
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 paths the languages translate that no command file has, by file. */
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 languages of `src/messages/`: what Discord shows of
2
- // each command in each language. A command has its texts in one place: in
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/convention.js';
6
+ import { checkLocales, } from '../commands/declaration.js';
7
7
  import { commandName } from '../commands/tree.js';
8
- /** The paths the languages translate that no command file has, by file. */
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) ?? 'src/messages/en-US.ts';
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
- const main = messages ? given.get(messages.default) : undefined;
57
- const description = main?.descriptions[messages.default];
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
- main?.optionLocales[option.name]?.descriptions[messages.default];
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 === messages.default)
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;