@discord/intl-loader-core 0.21.1 → 0.22.0-canary.558dd86

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/index.js CHANGED
@@ -3,6 +3,7 @@ const {
3
3
  isMessageDefinitionsFile,
4
4
  isMessageTranslationsFile,
5
5
  IntlCompiledMessageFormat,
6
+ IntlDatabaseInsertStrategy,
6
7
  } = require('@discord/intl-message-database');
7
8
 
8
9
  const { database } = require('./src/database');
@@ -24,6 +25,9 @@ module.exports = {
24
25
  // @ts-expect-error This is a const enum, which TypeScript doesn't like letting you export, even
25
26
  // though it's a tangible object that can be accessed just fine from normal JS.
26
27
  IntlCompiledMessageFormat,
28
+ // @ts-expect-error This is a const enum, which TypeScript doesn't like letting you export, even
29
+ // though it's a tangible object that can be accessed just fine from normal JS.
30
+ IntlDatabaseInsertStrategy,
27
31
  MessageDefinitionsTransformer,
28
32
  database,
29
33
  findAllTranslationFiles,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@discord/intl-loader-core",
3
- "version": "0.21.1",
3
+ "version": "0.22.0-canary.558dd86",
4
4
  "license": "MIT",
5
5
  "description": "Core utilities for writing loaders and transformers using @discord/intl",
6
6
  "author": "Jon Egeland",
@@ -28,7 +28,7 @@
28
28
  "dependencies": {
29
29
  "chokidar": "^3.6.0",
30
30
  "debug": "^4.3.6",
31
- "@discord/intl-message-database": "0.21.1"
31
+ "@discord/intl-message-database": "0.22.0-rc.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/debug": "^4.1.12",
package/src/processing.js CHANGED
@@ -1,12 +1,22 @@
1
1
  const path = require('node:path');
2
2
 
3
3
  const debug = require('debug')('intl:loader-core');
4
- const { IntlCompiledMessageFormat } = require('@discord/intl-message-database');
4
+ const {
5
+ IntlCompiledMessageFormat,
6
+ IntlDatabaseInsertStrategy,
7
+ } = require('@discord/intl-message-database');
5
8
 
6
9
  const { database } = require('./database');
7
10
  const { findAllTranslationFiles, getLocaleFromTranslationsFileName } = require('./util');
8
11
 
9
12
  /**
13
+ * @import {
14
+ * IntlMessagesFileDescriptor,
15
+ * IntlSourceFileInsertionData,
16
+ * ProcessDefinitionsResult,
17
+ * ProcessTranslationsResult
18
+ * } from '../types';
19
+ *
10
20
  * @typedef {{
11
21
  * format?: IntlCompiledMessageFormat,
12
22
  * bundleSecrets?: boolean,
@@ -63,7 +73,6 @@ function findAllDefinitionsFilesForTranslations(filePath) {
63
73
  for (const file of sourceFiles) {
64
74
  const source = database.getSourceFile(file);
65
75
  if (source.type === 'definition' && source.meta.translationsPath === expectedTranslationsPath) {
66
- console.log(file, source.meta);
67
76
  relevantPaths.push(source.file);
68
77
  }
69
78
  }
@@ -89,7 +98,7 @@ function findAllMessagesFiles(directories, defaultLocale = 'en-US') {
89
98
  *
90
99
  * @param {string[]} files
91
100
  * @param {string} defaultLocale
92
- * @returns {import('../types').IntlMessagesFileDescriptor[]}
101
+ * @returns {IntlMessagesFileDescriptor[]}
93
102
  */
94
103
  function filterAllMessagesFiles(files, defaultLocale = 'en-US') {
95
104
  return database.filterAllMessagesFiles(files, defaultLocale);
@@ -97,17 +106,19 @@ function filterAllMessagesFiles(files, defaultLocale = 'en-US') {
97
106
 
98
107
  /**
99
108
  * Iterate the given `files`, processing each one's content into the database. Processing is done
100
- * in parallel using native acceleration, and returns the list of all file names that were used.
109
+ * in parallel using native acceleration, and returns an object describing all of the changes that
110
+ * were processed.
101
111
  *
102
112
  * Note that this method _only_ operates by reading file contents from the system, it is not
103
113
  * possible to supply preloaded content through a Buffer in the same way as `processDefinitionsFile`
104
114
  * with a `sourceContent` argument.
105
115
  *
106
- * @param {import('../types').IntlMessagesFileDescriptor[]} files
107
- * @returns {import('../types').IntlMultiProcessingResult}
116
+ * @param {IntlMessagesFileDescriptor[]} files
117
+ * @param {IntlDatabaseInsertStrategy} [strategy]
118
+ * @returns {IntlSourceFileInsertionData[]}
108
119
  */
109
- function processAllMessagesFiles(files) {
110
- return database.processAllMessagesFiles(files);
120
+ function processAllMessagesFiles(files, strategy = IntlDatabaseInsertStrategy.Update) {
121
+ return database.processAllMessagesFiles(files, strategy);
111
122
  }
112
123
 
113
124
  /**
@@ -116,21 +127,28 @@ function processAllMessagesFiles(files) {
116
127
  * @param {{
117
128
  * processTranslations?: boolean,
118
129
  * locale?: string
130
+ * strategy?: IntlDatabaseInsertStrategy
119
131
  * }=} options
120
- * @returns {import('../types').ProcessDefinitionsResult}
132
+ * @returns {ProcessDefinitionsResult}
121
133
  */
122
134
  function processDefinitionsFile(sourcePath, sourceContent, options = {}) {
123
135
  const {
124
136
  processTranslations = false,
125
137
  // TODO: Make this more configurable/automatically determined.
126
138
  locale = 'en-US',
139
+ strategy = IntlDatabaseInsertStrategy.Update,
127
140
  } = options;
128
141
  debug(`[${sourcePath}] Processing definitions with locale "${locale}"`);
129
142
 
143
+ /** @type {IntlSourceFileInsertionData} */
144
+ let result;
130
145
  if (sourceContent != null) {
131
- database.processDefinitionsFileContent(sourcePath, sourceContent, locale);
146
+ result = database.processDefinitionsFileContent(sourcePath, sourceContent, locale, strategy);
132
147
  } else {
133
- database.processDefinitionsFile(sourcePath, locale);
148
+ result = database.processDefinitionsFile(sourcePath, locale, strategy);
149
+ }
150
+ if (result.errors.length > 0) {
151
+ return { succeeded: false, errors: result.errors };
134
152
  }
135
153
 
136
154
  const sourceFile = database.getSourceFile(sourcePath);
@@ -150,11 +168,16 @@ function processDefinitionsFile(sourcePath, sourceContent, options = {}) {
150
168
  );
151
169
 
152
170
  if (processTranslations) {
153
- const translationsResult = database.processAllTranslationFiles(translationsLocaleMap);
171
+ const translationsResult = database.processAllTranslationFiles(translationsLocaleMap, strategy);
172
+ const allErrors = translationsResult.flatMap((result) => result.errors);
173
+ if (allErrors.length > 0) {
174
+ return { succeeded: false, errors: allErrors };
175
+ }
154
176
  debug('[${sourcePath}] Finished processing all translations: %O', translationsResult);
155
177
  }
156
178
 
157
179
  return {
180
+ succeeded: true,
158
181
  sourceFile,
159
182
  locale,
160
183
  messageKeys,
@@ -169,15 +192,25 @@ function processDefinitionsFile(sourcePath, sourceContent, options = {}) {
169
192
  * @param {string=} sourceContent
170
193
  * @param {{
171
194
  * locale?: string,
195
+ * strategy?: IntlDatabaseInsertStrategy
172
196
  * }=} options
173
- * @returns {import('../types').ProcessTranslationsResult}
197
+ * @returns {ProcessTranslationsResult}
174
198
  */
175
199
  function processTranslationsFile(sourcePath, sourceContent, options = {}) {
176
- const { locale = getLocaleFromTranslationsFileName(sourcePath) } = options;
200
+ const {
201
+ locale = getLocaleFromTranslationsFileName(sourcePath),
202
+ strategy = IntlDatabaseInsertStrategy.Update,
203
+ } = options;
204
+
205
+ /** @type {IntlSourceFileInsertionData} */
206
+ let result;
177
207
  if (sourceContent) {
178
- database.processTranslationFileContent(sourcePath, locale, sourceContent);
208
+ result = database.processTranslationFileContent(sourcePath, locale, sourceContent, strategy);
179
209
  } else {
180
- database.processTranslationFile(sourcePath, locale);
210
+ result = database.processTranslationFile(sourcePath, locale, strategy);
211
+ }
212
+ if (result.errors.length > 0) {
213
+ return { succeeded: false, errors: result.errors };
181
214
  }
182
215
 
183
216
  const sourceFile = database.getSourceFile(sourcePath);
@@ -189,6 +222,7 @@ function processTranslationsFile(sourcePath, sourceContent, options = {}) {
189
222
  }
190
223
 
191
224
  return {
225
+ succeeded: true,
192
226
  sourceFile,
193
227
  locale,
194
228
  messageKeys: database.getSourceFileKeyMap(sourcePath),
package/src/watcher.js CHANGED
@@ -7,7 +7,6 @@ const {
7
7
  IntlCompiledMessageFormat,
8
8
  } = require('@discord/intl-message-database');
9
9
 
10
- const { database } = require('./database');
11
10
  const {
12
11
  processDefinitionsFile,
13
12
  precompileFileForLocale,
@@ -30,7 +29,7 @@ const DEFAULT_LOCALE = 'en-US';
30
29
 
31
30
  /**
32
31
  * @typedef {{
33
- * on(event: string, callback: (event: string, path: string) => void): void
32
+ * on(event: string, callback: (event: string, path: string) => void): void,
34
33
  * }} BasicWatcher
35
34
  */
36
35
 
@@ -53,7 +52,11 @@ async function watchMessagesFiles(
53
52
  ) {
54
53
  debug('Pre-initializing database with all discoverable messages files');
55
54
  const files = findAllMessagesFiles(watchedFolders);
56
- processAllMessagesFiles(files);
55
+ const result = processAllMessagesFiles(files);
56
+ const reprocessQueue = result
57
+ .filter((data) => data.errors.length > 0)
58
+ .map((data) => data.fileKey);
59
+
57
60
  if (once) {
58
61
  debug('Performing one-shot watch because `once` was true');
59
62
  // This is a weird hack around file watching just not working well on
@@ -80,8 +83,27 @@ async function watchMessagesFiles(
80
83
  ignoreInitial: skipInitial,
81
84
  persistent: !once,
82
85
  });
86
+
83
87
  watcher.on('all', (event, path) => {
84
- processAllMessagesFiles(filterAllMessagesFiles([path]));
88
+ const files = filterAllMessagesFiles([path]);
89
+ if (files.length === 0) return;
90
+
91
+ // Process the file that changed first, then reprocess all of the queued files from previous
92
+ // failures to hopefully get them to process successfully again.
93
+ const [result] = processAllMessagesFiles(files);
94
+ const failed = result.errors.length > 0 ? [result.fileKey] : [];
95
+ const secondResult = processAllMessagesFiles(
96
+ filterAllMessagesFiles([...reprocessQueue, ...failed]),
97
+ );
98
+ // For any files that are still failing, keep them in the queue for next time.
99
+ const secondFailures = secondResult
100
+ .filter((data) => data.errors.length > 0)
101
+ .map((data) => data.fileKey);
102
+ if (secondFailures.length > 0) {
103
+ debug('Failed to re-process files: %O. Next update will try again.', secondFailures);
104
+ }
105
+ reprocessQueue.splice(0, Infinity, ...secondFailures);
106
+
85
107
  debug(`watcher got event: ${event}, for path ${path}`);
86
108
  });
87
109
  for (const signal of ['SIGINT', 'SIGTERM', 'SIGQUIT']) {
@@ -136,16 +158,17 @@ async function precompileMessageDefinitionsFiles(
136
158
  `.compiled.messages.${assetExtension}`,
137
159
  );
138
160
  const result = processDefinitionsFile(filePath);
139
- precompileFileForLocale(filePath, result.locale, undefined, {
161
+ if (!result.succeeded) {
162
+ debug('[INTL Error] Failed to compile messages');
163
+ console.error(result.errors);
164
+ return;
165
+ }
166
+
167
+ precompileFileForLocale(filePath, result.locale, outputPath, {
140
168
  format,
141
169
  bundleSecrets,
142
170
  });
143
171
 
144
- database.processDefinitionsFile(filePath);
145
- database.precompile(filePath, DEFAULT_LOCALE, outputPath, {
146
- format: IntlCompiledMessageFormat.KeylessJson,
147
- bundleSecrets,
148
- });
149
172
  debug(`Wrote definitions to: ${outputPath}`);
150
173
  } catch (e) {
151
174
  debug('[INTL Error] Failed to compile messages');
package/types/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { IntlCompiledMessageFormat } from "@discord/intl-message-database";
2
+ import { IntlDatabaseInsertStrategy } from "@discord/intl-message-database";
2
3
  import { MessageDefinitionsTransformer } from "./src/transformer";
3
4
  import { database } from "./src/database";
4
5
  import { findAllTranslationFiles } from "./src/util";
@@ -15,4 +16,4 @@ import { findAllMessagesFiles } from "./src/processing";
15
16
  import { filterAllMessagesFiles } from "./src/processing";
16
17
  import { processAllMessagesFiles } from "./src/processing";
17
18
  import watcher = require("./src/watcher");
18
- export { IntlCompiledMessageFormat, MessageDefinitionsTransformer, database, findAllTranslationFiles, findAllDefinitionsFilesForTranslations, getLocaleFromTranslationsFileName, generateTypeDefinitions, hashMessageKey, isMessageDefinitionsFile, isMessageTranslationsFile, processDefinitionsFile, processTranslationsFile, precompileFileForLocale, findAllMessagesFiles, filterAllMessagesFiles, processAllMessagesFiles, watcher };
19
+ export { IntlCompiledMessageFormat, IntlDatabaseInsertStrategy, MessageDefinitionsTransformer, database, findAllTranslationFiles, findAllDefinitionsFilesForTranslations, getLocaleFromTranslationsFileName, generateTypeDefinitions, hashMessageKey, isMessageDefinitionsFile, isMessageTranslationsFile, processDefinitionsFile, processTranslationsFile, precompileFileForLocale, findAllMessagesFiles, filterAllMessagesFiles, processAllMessagesFiles, watcher };
@@ -32,21 +32,23 @@ export function findAllMessagesFiles(directories: string[], defaultLocale?: stri
32
32
  *
33
33
  * @param {string[]} files
34
34
  * @param {string} defaultLocale
35
- * @returns {import('../types').IntlMessagesFileDescriptor[]}
35
+ * @returns {IntlMessagesFileDescriptor[]}
36
36
  */
37
- export function filterAllMessagesFiles(files: string[], defaultLocale?: string): import("../types").IntlMessagesFileDescriptor[];
37
+ export function filterAllMessagesFiles(files: string[], defaultLocale?: string): IntlMessagesFileDescriptor[];
38
38
  /**
39
39
  * Iterate the given `files`, processing each one's content into the database. Processing is done
40
- * in parallel using native acceleration, and returns the list of all file names that were used.
40
+ * in parallel using native acceleration, and returns an object describing all of the changes that
41
+ * were processed.
41
42
  *
42
43
  * Note that this method _only_ operates by reading file contents from the system, it is not
43
44
  * possible to supply preloaded content through a Buffer in the same way as `processDefinitionsFile`
44
45
  * with a `sourceContent` argument.
45
46
  *
46
- * @param {import('../types').IntlMessagesFileDescriptor[]} files
47
- * @returns {import('../types').IntlMultiProcessingResult}
47
+ * @param {IntlMessagesFileDescriptor[]} files
48
+ * @param {IntlDatabaseInsertStrategy} [strategy]
49
+ * @returns {IntlSourceFileInsertionData[]}
48
50
  */
49
- export function processAllMessagesFiles(files: import("../types").IntlMessagesFileDescriptor[]): import("../types").IntlMultiProcessingResult;
51
+ export function processAllMessagesFiles(files: IntlMessagesFileDescriptor[], strategy?: IntlDatabaseInsertStrategy | undefined): IntlSourceFileInsertionData[];
50
52
  /**
51
53
  * Generate a `.d.ts` file containing TypeScript type definitions for all of the messages defined in
52
54
  * `sourcePath`. This method does not process `sourcePath` at all, meaning it expects the database
@@ -92,23 +94,32 @@ export function precompileFileForLocale(sourcePath: string, locale: string, outp
92
94
  * @param {{
93
95
  * processTranslations?: boolean,
94
96
  * locale?: string
97
+ * strategy?: IntlDatabaseInsertStrategy
95
98
  * }=} options
96
- * @returns {import('../types').ProcessDefinitionsResult}
99
+ * @returns {ProcessDefinitionsResult}
97
100
  */
98
101
  export function processDefinitionsFile(sourcePath: string, sourceContent?: string | undefined, options?: {
99
102
  processTranslations?: boolean;
100
103
  locale?: string;
101
- } | undefined): import("../types").ProcessDefinitionsResult;
104
+ strategy?: IntlDatabaseInsertStrategy;
105
+ } | undefined): ProcessDefinitionsResult;
102
106
  /**
103
107
  *
104
108
  * @param {string} sourcePath
105
109
  * @param {string=} sourceContent
106
110
  * @param {{
107
111
  * locale?: string,
112
+ * strategy?: IntlDatabaseInsertStrategy
108
113
  * }=} options
109
- * @returns {import('../types').ProcessTranslationsResult}
114
+ * @returns {ProcessTranslationsResult}
110
115
  */
111
116
  export function processTranslationsFile(sourcePath: string, sourceContent?: string | undefined, options?: {
112
117
  locale?: string;
113
- } | undefined): import("../types").ProcessTranslationsResult;
118
+ strategy?: IntlDatabaseInsertStrategy;
119
+ } | undefined): ProcessTranslationsResult;
114
120
  import { IntlCompiledMessageFormat } from "@discord/intl-message-database";
121
+ import type { IntlMessagesFileDescriptor } from '../types';
122
+ import { IntlDatabaseInsertStrategy } from "@discord/intl-message-database";
123
+ import type { IntlSourceFileInsertionData } from '../types';
124
+ import type { ProcessDefinitionsResult } from '../types';
125
+ import type { ProcessTranslationsResult } from '../types';
@@ -3,7 +3,7 @@ export type BasicWatcher = {
3
3
  };
4
4
  /**
5
5
  * @typedef {{
6
- * on(event: string, callback: (event: string, path: string) => void): void
6
+ * on(event: string, callback: (event: string, path: string) => void): void,
7
7
  * }} BasicWatcher
8
8
  */
9
9
  /**
package/types/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { IntlSourceFile } from '@discord/intl-message-database';
1
+ import { IntlSourceFile, IntlSourceFileError } from '@discord/intl-message-database';
2
2
 
3
3
  export type * from '@discord/intl-message-database';
4
4
 
@@ -72,11 +72,17 @@ export interface MessageDefinitionsTransformerOptions {
72
72
  bindMode?: 'proxy' | 'literal';
73
73
  }
74
74
 
75
- /**
76
- * The result of calling `processDefinitionsFile`, including the created source file, locale map,
77
- * and more.
78
- */
79
- export interface ProcessDefinitionsResult {
75
+ export interface IntlProcessingError {
76
+ succeeded: false;
77
+ errors: IntlSourceFileError[];
78
+ }
79
+
80
+ export interface ProcessDefinitionsSuccess {
81
+ /**
82
+ * Whether the processing was successful as a whole. If any part of the process contained errors,
83
+ * including processing translations alongside the definitions file, this will be set to `false`.
84
+ */
85
+ succeeded: true;
80
86
  /**
81
87
  * Direct source file from the database that was created or updated by this process.
82
88
  */
@@ -108,10 +114,13 @@ export interface ProcessDefinitionsResult {
108
114
  }
109
115
 
110
116
  /**
111
- * The result of calling `processTranslationsFile`, including the created source file, locale map,
117
+ * The result of calling `processDefinitionsFile`, including the created source file, locale map,
112
118
  * and more.
113
119
  */
114
- export interface ProcessTranslationsResult {
120
+ export type ProcessDefinitionsResult = ProcessDefinitionsSuccess | IntlProcessingError;
121
+
122
+ export interface ProcessTranslationsSuccess {
123
+ succeeded: true;
115
124
  /**
116
125
  * Direct source file from the database that was created or updated by this process.
117
126
  */
@@ -128,3 +137,9 @@ export interface ProcessTranslationsResult {
128
137
  */
129
138
  locale: string;
130
139
  }
140
+
141
+ /**
142
+ * The result of calling `processTranslationsFile`, including the created source file, locale map,
143
+ * and more.
144
+ */
145
+ export type ProcessTranslationsResult = ProcessTranslationsSuccess | IntlProcessingError;
package/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { IntlSourceFile } from '@discord/intl-message-database';
1
+ import { IntlSourceFile, IntlSourceFileError } from '@discord/intl-message-database';
2
2
 
3
3
  export type * from '@discord/intl-message-database';
4
4
 
@@ -72,11 +72,17 @@ export interface MessageDefinitionsTransformerOptions {
72
72
  bindMode?: 'proxy' | 'literal';
73
73
  }
74
74
 
75
- /**
76
- * The result of calling `processDefinitionsFile`, including the created source file, locale map,
77
- * and more.
78
- */
79
- export interface ProcessDefinitionsResult {
75
+ export interface IntlProcessingError {
76
+ succeeded: false;
77
+ errors: IntlSourceFileError[];
78
+ }
79
+
80
+ export interface ProcessDefinitionsSuccess {
81
+ /**
82
+ * Whether the processing was successful as a whole. If any part of the process contained errors,
83
+ * including processing translations alongside the definitions file, this will be set to `false`.
84
+ */
85
+ succeeded: true;
80
86
  /**
81
87
  * Direct source file from the database that was created or updated by this process.
82
88
  */
@@ -108,10 +114,13 @@ export interface ProcessDefinitionsResult {
108
114
  }
109
115
 
110
116
  /**
111
- * The result of calling `processTranslationsFile`, including the created source file, locale map,
117
+ * The result of calling `processDefinitionsFile`, including the created source file, locale map,
112
118
  * and more.
113
119
  */
114
- export interface ProcessTranslationsResult {
120
+ export type ProcessDefinitionsResult = ProcessDefinitionsSuccess | IntlProcessingError;
121
+
122
+ export interface ProcessTranslationsSuccess {
123
+ succeeded: true;
115
124
  /**
116
125
  * Direct source file from the database that was created or updated by this process.
117
126
  */
@@ -128,3 +137,9 @@ export interface ProcessTranslationsResult {
128
137
  */
129
138
  locale: string;
130
139
  }
140
+
141
+ /**
142
+ * The result of calling `processTranslationsFile`, including the created source file, locale map,
143
+ * and more.
144
+ */
145
+ export type ProcessTranslationsResult = ProcessTranslationsSuccess | IntlProcessingError;