@saykit/config 0.0.0-beta-20260330102339 → 0.0.0-beta-20260418203312
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/commands/index.mjs +176 -286
- package/dist/features/loader/index.d.mts +37 -0
- package/dist/features/loader/index.mjs +2 -0
- package/dist/features/messages/index.d.mts +62 -0
- package/dist/features/messages/index.mjs +91 -0
- package/dist/hash-CA-PhXGF.mjs +11 -0
- package/dist/index.d.mts +24 -117
- package/dist/index.mjs +3 -4
- package/dist/resolve-BifzXxrw.mjs +93 -0
- package/dist/shapes-B0roKIQa.d.mts +243 -0
- package/dist/shapes-CgdfBNBI.mjs +47 -0
- package/package.json +20 -6
package/dist/commands/index.mjs
CHANGED
|
@@ -1,139 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { t as resolveConfig } from "../resolve-BifzXxrw.mjs";
|
|
3
|
+
import { t as generateHash } from "../hash-CA-PhXGF.mjs";
|
|
3
4
|
import { Command, program } from "@commander-js/extra-typings";
|
|
4
|
-
import { dirname,
|
|
5
|
-
import picomatch from "picomatch";
|
|
6
|
-
import * as z from "zod";
|
|
7
|
-
import { access, glob, mkdir, readFile, rm, watch, writeFile } from "node:fs/promises";
|
|
8
|
-
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
9
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
//#region src/shapes.ts
|
|
13
|
-
const Message = z.object({
|
|
14
|
-
message: z.string(),
|
|
15
|
-
translation: z.string().optional(),
|
|
16
|
-
id: z.string().optional(),
|
|
17
|
-
context: z.string().optional(),
|
|
18
|
-
comments: z.string().array(),
|
|
19
|
-
references: z.string().array()
|
|
20
|
-
});
|
|
21
|
-
const Formatter = z.object({
|
|
22
|
-
extension: z.templateLiteral([".", z.string()]),
|
|
23
|
-
parse: z.custom((v) => typeof v === "function"),
|
|
24
|
-
stringify: z.custom((v) => typeof v === "function")
|
|
25
|
-
});
|
|
26
|
-
const Bucket = z.object({
|
|
27
|
-
include: z.array(z.string()),
|
|
28
|
-
exclude: z.array(z.string()).optional(),
|
|
29
|
-
output: z.templateLiteral([
|
|
30
|
-
z.string(),
|
|
31
|
-
"{locale}",
|
|
32
|
-
z.string(),
|
|
33
|
-
".{extension}"
|
|
34
|
-
]),
|
|
35
|
-
formatter: Formatter
|
|
36
|
-
}).transform((v) => ({
|
|
37
|
-
...v,
|
|
38
|
-
match: picomatch(v.include, { ignore: v.exclude })
|
|
39
|
-
}));
|
|
40
|
-
const Configuration = z.object({
|
|
41
|
-
sourceLocale: z.string(),
|
|
42
|
-
locales: z.tuple([z.string()], z.string()),
|
|
43
|
-
fallbackLocales: z.record(z.string(), z.array(z.string())).optional(),
|
|
44
|
-
buckets: z.array(Bucket)
|
|
45
|
-
}).refine((c) => c.sourceLocale === c.locales[0], "sourceLocale must be the same as locales[0]");
|
|
46
|
-
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region src/features/loader/explorer.ts
|
|
49
|
-
function getFilesToTry(name) {
|
|
50
|
-
return [
|
|
51
|
-
`${name}.config.js`,
|
|
52
|
-
`${name}.config.cjs`,
|
|
53
|
-
`${name}.config.mjs`,
|
|
54
|
-
`${name}.config.ts`,
|
|
55
|
-
`${name}.config.mts`,
|
|
56
|
-
`${name}.config.cts`
|
|
57
|
-
];
|
|
58
|
-
}
|
|
59
|
-
async function findConfigFile(moduleName, projectDir) {
|
|
60
|
-
for (const fileName of getFilesToTry(moduleName)) try {
|
|
61
|
-
const id = join(projectDir, fileName);
|
|
62
|
-
await access(id);
|
|
63
|
-
return {
|
|
64
|
-
id,
|
|
65
|
-
content: await readFile(id, "utf8")
|
|
66
|
-
};
|
|
67
|
-
} catch {}
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
//#endregion
|
|
72
|
-
//#region src/features/loader/loaders.ts
|
|
73
|
-
const js = async (path, _content) => {
|
|
74
|
-
try {
|
|
75
|
-
const { href } = pathToFileURL(path);
|
|
76
|
-
return (await import(href)).default;
|
|
77
|
-
} catch (importError) {
|
|
78
|
-
try {
|
|
79
|
-
const require = globalThis.require ?? createRequire(path);
|
|
80
|
-
const module = require(path);
|
|
81
|
-
delete require.cache[require.resolve(path)];
|
|
82
|
-
return module?.default ?? module;
|
|
83
|
-
} catch (requireError) {
|
|
84
|
-
throw new Error("Failed to import module", { cause: [importError, requireError] });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
const ts = async (path, content) => {
|
|
89
|
-
const ts = await import("typescript");
|
|
90
|
-
const outputPath = `${path}.${Date.now()}.js`;
|
|
91
|
-
try {
|
|
92
|
-
const tsConfigPath = ts.findConfigFile(dirname(path), ts.sys.fileExists) || "tsconfig.json";
|
|
93
|
-
const { config: tsConfig, error } = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
|
|
94
|
-
if (error) throw error;
|
|
95
|
-
tsConfig.compilerOptions = {
|
|
96
|
-
...tsConfig.compilerOptions,
|
|
97
|
-
module: ts.ModuleKind.ES2022,
|
|
98
|
-
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
99
|
-
target: ts.ScriptTarget.ES2022,
|
|
100
|
-
noEmit: false
|
|
101
|
-
};
|
|
102
|
-
const transpiledContent = ts.transpileModule(content, tsConfig).outputText;
|
|
103
|
-
await writeFile(outputPath, transpiledContent);
|
|
104
|
-
return await js(outputPath, transpiledContent);
|
|
105
|
-
} catch (error) {
|
|
106
|
-
throw new Error("Failed to import module", { cause: error });
|
|
107
|
-
} finally {
|
|
108
|
-
if (ts.sys.fileExists(outputPath)) await rm(outputPath);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
var loaders_default = Object.freeze({
|
|
112
|
-
".js": js,
|
|
113
|
-
".mjs": js,
|
|
114
|
-
".cjs": js,
|
|
115
|
-
".ts": ts,
|
|
116
|
-
".mts": ts,
|
|
117
|
-
".cts": ts
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
//#endregion
|
|
121
|
-
//#region src/features/loader/resolve.ts
|
|
122
|
-
async function useConfig(name = "saykit") {
|
|
123
|
-
const file = await findConfigFile(name, process.cwd());
|
|
124
|
-
if (!file) throw new Error(`Could not find config file for "${name}"`);
|
|
125
|
-
const ext = extname(file.id).toLowerCase();
|
|
126
|
-
const loader = ext in loaders_default ? loaders_default[ext] : null;
|
|
127
|
-
if (!loader) throw new Error(`Unsupported config file type "${ext}" for "${name}"`);
|
|
128
|
-
let config = await loader(file.id, file.content);
|
|
129
|
-
if (!config || typeof config !== "object") throw new Error(`Invalid config file for "${name}"`);
|
|
130
|
-
if (config && typeof config === "object" && "saykit" in config) config = config.saykit;
|
|
131
|
-
const result = await Configuration.safeParseAsync(config);
|
|
132
|
-
if (result.error) throw new Error(`Invalid config file for "${name}"`, { cause: result.error });
|
|
133
|
-
return result.data;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
//#endregion
|
|
7
|
+
import { glob, mkdir, readFile, watch, writeFile } from "node:fs/promises";
|
|
137
8
|
//#region src/features/logger.ts
|
|
138
9
|
const RESET = "\x1B[0m";
|
|
139
10
|
const DIM = "\x1B[2m";
|
|
@@ -171,108 +42,7 @@ var Logger = class {
|
|
|
171
42
|
if (this.#verbose) this.log(` ${DIM}→ ${message}${RESET}`);
|
|
172
43
|
}
|
|
173
44
|
};
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region src/features/extract.ts
|
|
178
|
-
async function extractMessages(path) {
|
|
179
|
-
return collectMessages(path, await readFile(path, "utf8").catch(() => "")).map((m) => ({
|
|
180
|
-
message: m.toICUString(),
|
|
181
|
-
translation: m.toICUString(),
|
|
182
|
-
id: m.descriptor.id,
|
|
183
|
-
context: m.descriptor.context,
|
|
184
|
-
comments: m.comments,
|
|
185
|
-
references: m.references.map((r) => relative(process.cwd(), r).replaceAll("\\", "/"))
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
function mergeUnique(...items) {
|
|
189
|
-
return Array.from(new Set(items.flat()));
|
|
190
|
-
}
|
|
191
|
-
function mergeMessages(messages) {
|
|
192
|
-
const mergedMessages = messages.reduce((map, message) => {
|
|
193
|
-
const key = message.id ?? generateHash(message.message, message.context);
|
|
194
|
-
const existing = map.get(key) ?? message;
|
|
195
|
-
map.set(key, {
|
|
196
|
-
...existing,
|
|
197
|
-
comments: mergeUnique(...existing.comments, ...message.comments),
|
|
198
|
-
references: mergeUnique(...existing.references, ...message.references)
|
|
199
|
-
});
|
|
200
|
-
return map;
|
|
201
|
-
}, /* @__PURE__ */ new Map());
|
|
202
|
-
return Array.from(mergedMessages.values());
|
|
203
|
-
}
|
|
204
|
-
function reconcileMessages(existingMessages, newMessages) {
|
|
205
|
-
const existingMessagesMap = existingMessages.reduce((map, message) => {
|
|
206
|
-
const key = message.id ?? generateHash(message.message, message.context);
|
|
207
|
-
map.set(key, message);
|
|
208
|
-
return map;
|
|
209
|
-
}, /* @__PURE__ */ new Map());
|
|
210
|
-
const updatedMessagesMap = newMessages.reduce((map, message) => {
|
|
211
|
-
const key = message.id ?? generateHash(message.message, message.context);
|
|
212
|
-
const existingMessage = existingMessagesMap.get(key);
|
|
213
|
-
map.set(key, {
|
|
214
|
-
message: message.message,
|
|
215
|
-
translation: void 0,
|
|
216
|
-
...existingMessage,
|
|
217
|
-
id: message.id,
|
|
218
|
-
context: message.context,
|
|
219
|
-
comments: message.comments,
|
|
220
|
-
references: message.references
|
|
221
|
-
});
|
|
222
|
-
return map;
|
|
223
|
-
}, /* @__PURE__ */ new Map());
|
|
224
|
-
return Array.from(updatedMessagesMap.values());
|
|
225
|
-
}
|
|
226
|
-
function expandOutputPath(bucket, locale, extension = bucket.formatter.extension) {
|
|
227
|
-
return resolve(bucket.output.replaceAll("{locale}", locale).replaceAll("{extension}", extension.slice(1)));
|
|
228
|
-
}
|
|
229
|
-
async function readMessagesFromDisk(bucket, locale, path = expandOutputPath(bucket, locale)) {
|
|
230
|
-
const content = await readFile(path, "utf8").catch(() => "");
|
|
231
|
-
if (!content) return [];
|
|
232
|
-
return await bucket.formatter.parse(content, { locale });
|
|
233
|
-
}
|
|
234
|
-
async function writeMessagesToDisk(bucket, locale, messages, path = expandOutputPath(bucket, locale)) {
|
|
235
|
-
const content = await bucket.formatter.stringify(messages, { locale });
|
|
236
|
-
await mkdir(dirname(path), { recursive: true });
|
|
237
|
-
await writeFile(path, content);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
//#endregion
|
|
241
|
-
//#region src/features/compile.ts
|
|
242
|
-
async function hydrateTranslations(cache, config, bucket, locale, messages) {
|
|
243
|
-
if (cache.has(locale)) return cache.get(locale);
|
|
244
|
-
const translations = await applyFallbackTranslations(cache, config, bucket, locale, messages);
|
|
245
|
-
cache.set(locale, translations);
|
|
246
|
-
return translations;
|
|
247
|
-
}
|
|
248
|
-
function getFallbackChain(config, locale) {
|
|
249
|
-
return [...config.fallbackLocales?.[locale] ?? [], config.sourceLocale];
|
|
250
|
-
}
|
|
251
|
-
async function applyFallbackTranslations(cache, config, bucket, locale, messages) {
|
|
252
|
-
const fallbacks = getFallbackChain(config, locale);
|
|
253
|
-
const translations = {};
|
|
254
|
-
for (const message of messages) {
|
|
255
|
-
const key = message.id ?? generateHash(message.message, message.context);
|
|
256
|
-
if (message.translation) {
|
|
257
|
-
translations[key] = message.translation;
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
for (const fallback of fallbacks) {
|
|
261
|
-
const fallbackMessages = await hydrateTranslations(cache, config, bucket, fallback, messages);
|
|
262
|
-
if (fallbackMessages[key]) {
|
|
263
|
-
translations[key] = fallbackMessages[key];
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return translations;
|
|
269
|
-
}
|
|
270
|
-
async function writeTranslationsToDisk(bucket, locale, translations, path = expandOutputPath(bucket, locale, ".json")) {
|
|
271
|
-
const content = JSON.stringify(translations, null, 2);
|
|
272
|
-
await mkdir(dirname(path), { recursive: true });
|
|
273
|
-
await writeFile(path, content);
|
|
274
|
-
}
|
|
275
|
-
|
|
45
|
+
new AsyncLocalStorage({ defaultValue: new Logger() });
|
|
276
46
|
//#endregion
|
|
277
47
|
//#region src/features/watch.ts
|
|
278
48
|
/**
|
|
@@ -316,10 +86,61 @@ async function* watchDebounced(path, options, delay = 300) {
|
|
|
316
86
|
yield next;
|
|
317
87
|
} else await new Promise((r) => setTimeout(r, 10));
|
|
318
88
|
}
|
|
319
|
-
|
|
320
89
|
//#endregion
|
|
321
|
-
//#region src/features/
|
|
322
|
-
function
|
|
90
|
+
//#region src/features/catalogue/path.ts
|
|
91
|
+
function expandBucketOutputPath(bucket, locale, extension = bucket.formatter.extension) {
|
|
92
|
+
return resolve(bucket.output.replaceAll("{locale}", locale).replaceAll("{extension}", extension.slice(1)));
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/features/catalogue/storage.ts
|
|
96
|
+
async function readCatalogueMessages(bucket, locale, path = expandBucketOutputPath(bucket, locale)) {
|
|
97
|
+
const content = await readFile(path, "utf8").catch(() => "");
|
|
98
|
+
if (!content) return [];
|
|
99
|
+
return bucket.formatter.parse(content, { locale });
|
|
100
|
+
}
|
|
101
|
+
async function writeCatalogueMessages(bucket, locale, messages, path = expandBucketOutputPath(bucket, locale)) {
|
|
102
|
+
const content = bucket.formatter.stringify(messages, { locale });
|
|
103
|
+
await mkdir(dirname(path), { recursive: true });
|
|
104
|
+
await writeFile(path, content);
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/features/runtime/translations.ts
|
|
108
|
+
function getFallbackLocaleChain(config, locale) {
|
|
109
|
+
return [...config.fallbackLocales?.[locale] ?? [], config.sourceLocale];
|
|
110
|
+
}
|
|
111
|
+
async function hydrateTranslations(cache, config, bucket, locale, messages) {
|
|
112
|
+
if (cache.has(locale)) return cache.get(locale);
|
|
113
|
+
const translations = await applyFallbackTranslations(cache, config, bucket, locale, messages);
|
|
114
|
+
cache.set(locale, translations);
|
|
115
|
+
return translations;
|
|
116
|
+
}
|
|
117
|
+
async function applyFallbackTranslations(cache, config, bucket, locale, messages) {
|
|
118
|
+
const fallbackLocales = getFallbackLocaleChain(config, locale);
|
|
119
|
+
const translations = {};
|
|
120
|
+
for (const message of messages) {
|
|
121
|
+
const key = message.id ?? generateHash(message.message, message.context);
|
|
122
|
+
if (message.translation) {
|
|
123
|
+
translations[key] = message.translation;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
for (const fallbackLocale of fallbackLocales) {
|
|
127
|
+
const fallbackTranslations = await hydrateTranslations(cache, config, bucket, fallbackLocale, await readCatalogueMessages(bucket, fallbackLocale));
|
|
128
|
+
if (fallbackTranslations[key]) {
|
|
129
|
+
translations[key] = fallbackTranslations[key];
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return translations;
|
|
135
|
+
}
|
|
136
|
+
async function writeRuntimeTranslations(bucket, locale, translations, path = expandBucketOutputPath(bucket, locale, ".json")) {
|
|
137
|
+
const content = JSON.stringify(translations, null, 2);
|
|
138
|
+
await mkdir(dirname(path), { recursive: true });
|
|
139
|
+
await writeFile(path, content);
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/features/workers/shared.ts
|
|
143
|
+
function normalisePathForLogs(path) {
|
|
323
144
|
return relative(process.cwd(), path).replaceAll("\\", "/");
|
|
324
145
|
}
|
|
325
146
|
var BucketWorker = class {
|
|
@@ -332,66 +153,137 @@ var BucketWorker = class {
|
|
|
332
153
|
this.logger = logger;
|
|
333
154
|
}
|
|
334
155
|
};
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/features/workers/compile-worker.ts
|
|
158
|
+
var BucketCompileWorker = class extends BucketWorker {
|
|
159
|
+
#translationsByLocale = /* @__PURE__ */ new Map();
|
|
160
|
+
async compileAll() {
|
|
161
|
+
this.logger.info(`Compiling bucket: ${this.bucket.include}`);
|
|
162
|
+
for (const locale of this.config.locales) await this.compileLocale(locale);
|
|
163
|
+
this.logger.success(`Compilation complete for bucket: ${this.bucket.include}`);
|
|
164
|
+
}
|
|
165
|
+
async compileLocale(locale) {
|
|
166
|
+
this.logger.step(`Compiling locale: ${locale}`);
|
|
167
|
+
const messages = await readCatalogueMessages(this.bucket, locale);
|
|
168
|
+
const translations = await hydrateTranslations(this.#translationsByLocale, this.config, this.bucket, locale, messages);
|
|
169
|
+
this.logger.step(`Writing runtime file for ${locale}`);
|
|
170
|
+
await writeRuntimeTranslations(this.bucket, locale, translations);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/features/catalogue/extractor.ts
|
|
175
|
+
async function extractMessagesFromFile(path, bucket) {
|
|
176
|
+
const content = await readFile(path, "utf8").catch(() => "");
|
|
177
|
+
if (!content) return [];
|
|
178
|
+
return bucket.transformer.extract(content, path).map((message) => ({
|
|
179
|
+
...message,
|
|
180
|
+
translation: message.translation ?? message.message,
|
|
181
|
+
references: message.references.map((reference) => relative(process.cwd(), reference).replaceAll("\\", "/"))
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/features/catalogue/merge.ts
|
|
186
|
+
function mergeUnique(...items) {
|
|
187
|
+
return Array.from(new Set(items.flat()));
|
|
188
|
+
}
|
|
189
|
+
function getMessageKey(message) {
|
|
190
|
+
return message.id ?? generateHash(message.message, message.context);
|
|
191
|
+
}
|
|
192
|
+
function mergeExtractedMessages(messages) {
|
|
193
|
+
const mergedMessages = messages.reduce((map, message) => {
|
|
194
|
+
const key = getMessageKey(message);
|
|
195
|
+
const existing = map.get(key) ?? message;
|
|
196
|
+
map.set(key, {
|
|
197
|
+
...existing,
|
|
198
|
+
comments: mergeUnique(...existing.comments, ...message.comments),
|
|
199
|
+
references: mergeUnique(...existing.references, ...message.references)
|
|
200
|
+
});
|
|
201
|
+
return map;
|
|
202
|
+
}, /* @__PURE__ */ new Map());
|
|
203
|
+
return Array.from(mergedMessages.values());
|
|
204
|
+
}
|
|
205
|
+
function reconcileLocaleMessages(existingMessages, nextMessages) {
|
|
206
|
+
const existingMessagesByKey = existingMessages.reduce((map, message) => {
|
|
207
|
+
map.set(getMessageKey(message), message);
|
|
208
|
+
return map;
|
|
209
|
+
}, /* @__PURE__ */ new Map());
|
|
210
|
+
const reconciledMessages = nextMessages.reduce((map, message) => {
|
|
211
|
+
const key = getMessageKey(message);
|
|
212
|
+
const existingMessage = existingMessagesByKey.get(key);
|
|
213
|
+
map.set(key, {
|
|
214
|
+
message: message.message,
|
|
215
|
+
translation: void 0,
|
|
216
|
+
...existingMessage,
|
|
217
|
+
id: message.id,
|
|
218
|
+
context: message.context,
|
|
219
|
+
comments: message.comments,
|
|
220
|
+
references: message.references
|
|
221
|
+
});
|
|
222
|
+
return map;
|
|
223
|
+
}, /* @__PURE__ */ new Map());
|
|
224
|
+
return Array.from(reconciledMessages.values());
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/features/workers/extract-worker.ts
|
|
335
228
|
var BucketExtractWorker = class extends BucketWorker {
|
|
336
|
-
|
|
229
|
+
#indexedMessagesByPath = /* @__PURE__ */ new Map();
|
|
337
230
|
get messages() {
|
|
338
|
-
return Array.from(this.
|
|
231
|
+
return Array.from(this.#indexedMessagesByPath.values()).flat();
|
|
339
232
|
}
|
|
340
|
-
async indexPath(path) {
|
|
341
|
-
const
|
|
342
|
-
this.logger.step(`Processing ${
|
|
343
|
-
const messages = await
|
|
344
|
-
if (!messages.length)
|
|
345
|
-
|
|
346
|
-
|
|
233
|
+
async #indexPath(path) {
|
|
234
|
+
const relativePath = normalisePathForLogs(path);
|
|
235
|
+
this.logger.step(`Processing ${relativePath}`);
|
|
236
|
+
const messages = await extractMessagesFromFile(path, this.bucket);
|
|
237
|
+
if (!messages.length) {
|
|
238
|
+
if (this.#indexedMessagesByPath.has(path)) {
|
|
239
|
+
this.#indexedMessagesByPath.delete(path);
|
|
240
|
+
this.logger.step(`Removed stale entries for ${relativePath}`);
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
this.#indexedMessagesByPath.set(path, messages);
|
|
246
|
+
this.logger.step(`Found ${messages.length} message(s) in ${relativePath}`);
|
|
347
247
|
return true;
|
|
348
248
|
}
|
|
349
249
|
async scanAll() {
|
|
350
250
|
this.logger.info(`Scanning bucket: ${this.bucket.include}`);
|
|
351
251
|
const paths = await globBucket(this.bucket);
|
|
352
252
|
this.logger.step(`Found ${paths.length} file(s)`);
|
|
353
|
-
await Promise.all(paths.map((
|
|
253
|
+
await Promise.all(paths.map((path) => this.#indexPath(path)));
|
|
354
254
|
this.logger.info(`Total extracted messages: ${this.messages.length}`);
|
|
355
255
|
}
|
|
356
256
|
async writeAll() {
|
|
357
|
-
const
|
|
358
|
-
this.logger.info(`Writing ${
|
|
257
|
+
const mergedMessages = mergeExtractedMessages(this.messages);
|
|
258
|
+
this.logger.info(`Writing ${mergedMessages.length} messages to locales`);
|
|
359
259
|
for (const locale of this.config.locales) {
|
|
360
260
|
this.logger.step(`Writing locale file for ${locale} to disk`);
|
|
361
|
-
const existingMessages = await
|
|
362
|
-
const
|
|
363
|
-
await
|
|
261
|
+
const existingMessages = await readCatalogueMessages(this.bucket, locale);
|
|
262
|
+
const nextMessages = locale === this.config.sourceLocale ? mergedMessages : reconcileLocaleMessages(existingMessages, mergedMessages);
|
|
263
|
+
await writeCatalogueMessages(this.bucket, locale, nextMessages);
|
|
364
264
|
}
|
|
365
265
|
this.logger.success(`Extraction complete for bucket: ${this.bucket.include}`);
|
|
366
266
|
}
|
|
367
267
|
async updatePath(path) {
|
|
368
|
-
const changed = await this
|
|
268
|
+
const changed = await this.#indexPath(path);
|
|
369
269
|
if (changed) await this.writeAll();
|
|
370
270
|
return changed;
|
|
371
271
|
}
|
|
372
272
|
};
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
async compileAll() {
|
|
376
|
-
this.logger.info(`Compiling bucket: ${this.bucket.include}`);
|
|
377
|
-
for (const locale of this.config.locales) await this.compileLocale(locale);
|
|
378
|
-
this.logger.success(`Compilation complete for bucket: ${this.bucket.include}`);
|
|
379
|
-
}
|
|
380
|
-
async compileLocale(locale) {
|
|
381
|
-
this.logger.step(`Compiling locale: ${locale}`);
|
|
382
|
-
const messages = await readMessagesFromDisk(this.bucket, locale);
|
|
383
|
-
const translations = await hydrateTranslations(this.cache, this.config, this.bucket, locale, messages);
|
|
384
|
-
this.logger.step(`Writing runtime file for ${locale}`);
|
|
385
|
-
await writeTranslationsToDisk(this.bucket, locale, translations);
|
|
386
|
-
}
|
|
387
|
-
};
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/features/workers/build-worker.ts
|
|
388
275
|
var BucketBuildWorker = class extends BucketWorker {
|
|
389
276
|
extract;
|
|
390
277
|
compile;
|
|
391
278
|
constructor(config, bucket, logger) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
279
|
+
const parameters = [
|
|
280
|
+
config,
|
|
281
|
+
bucket,
|
|
282
|
+
logger
|
|
283
|
+
];
|
|
284
|
+
super(...parameters);
|
|
285
|
+
this.extract = new BucketExtractWorker(...parameters);
|
|
286
|
+
this.compile = new BucketCompileWorker(...parameters);
|
|
395
287
|
}
|
|
396
288
|
async buildAll() {
|
|
397
289
|
await this.extract.scanAll();
|
|
@@ -404,41 +296,38 @@ var BucketBuildWorker = class extends BucketWorker {
|
|
|
404
296
|
if (!event.filename || !this.bucket.match(event.filename)) continue;
|
|
405
297
|
const filePath = join(process.cwd(), event.filename);
|
|
406
298
|
if (await this.extract.updatePath(filePath)) {
|
|
407
|
-
this.logger.info(`Recompiling due to changes in ${
|
|
299
|
+
this.logger.info(`Recompiling due to changes in ${normalisePathForLogs(filePath)}`);
|
|
408
300
|
await this.compile.compileAll();
|
|
409
301
|
}
|
|
410
302
|
}
|
|
411
303
|
}
|
|
412
304
|
};
|
|
413
|
-
|
|
414
305
|
//#endregion
|
|
415
306
|
//#region src/commands/build.ts
|
|
416
307
|
var build_default = new Command().name("build").description("").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
|
|
417
|
-
const config = await
|
|
308
|
+
const config = await resolveConfig("saykit");
|
|
418
309
|
const logger = new Logger(options);
|
|
419
310
|
logger.header("🏗 Building Messages");
|
|
420
311
|
const tasks = config.buckets.map(async (bucket) => {
|
|
421
312
|
await new BucketBuildWorker(config, bucket, logger).buildAll();
|
|
422
313
|
});
|
|
423
|
-
await Promise.
|
|
314
|
+
await Promise.all(tasks);
|
|
424
315
|
});
|
|
425
|
-
|
|
426
316
|
//#endregion
|
|
427
317
|
//#region src/commands/compile.ts
|
|
428
318
|
var compile_default = new Command("compile").description("Compile translations into runtime-ready locale files").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
|
|
429
|
-
const config = await
|
|
319
|
+
const config = await resolveConfig("saykit");
|
|
430
320
|
const logger = new Logger(options);
|
|
431
321
|
logger.header("🛠 Compiling Translations");
|
|
432
322
|
const tasks = config.buckets.map(async (bucket) => {
|
|
433
323
|
await new BucketCompileWorker(config, bucket, logger).compileAll();
|
|
434
324
|
});
|
|
435
|
-
await Promise.
|
|
325
|
+
await Promise.all(tasks);
|
|
436
326
|
});
|
|
437
|
-
|
|
438
327
|
//#endregion
|
|
439
328
|
//#region src/commands/extract.ts
|
|
440
329
|
var extract_default = new Command("extract").description("Extract messages from source files").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
|
|
441
|
-
const config = await
|
|
330
|
+
const config = await resolveConfig("saykit");
|
|
442
331
|
const logger = new Logger(options);
|
|
443
332
|
logger.header("🛠 Extracting Messages");
|
|
444
333
|
const tasks = config.buckets.map(async (bucket) => {
|
|
@@ -446,13 +335,14 @@ var extract_default = new Command("extract").description("Extract messages from
|
|
|
446
335
|
await worker.scanAll();
|
|
447
336
|
await worker.writeAll();
|
|
448
337
|
});
|
|
449
|
-
await Promise.allSettled(tasks);
|
|
338
|
+
const rejections = (await Promise.allSettled(tasks)).filter((r) => r.status === "rejected");
|
|
339
|
+
if (rejections.length > 0) {
|
|
340
|
+
const errors = rejections.map((r) => r.reason).join("\n");
|
|
341
|
+
throw new Error(`Bucket extraction failed:\n${errors}`);
|
|
342
|
+
}
|
|
450
343
|
});
|
|
451
|
-
|
|
452
344
|
//#endregion
|
|
453
345
|
//#region src/commands/index.ts
|
|
454
346
|
program.name("saykit").helpOption("-h, --help", "Display help for command").helpCommand("help [command]", "Display help for command").addCommand(extract_default).addCommand(compile_default).addCommand(build_default).parse();
|
|
455
|
-
|
|
456
347
|
//#endregion
|
|
457
|
-
export {
|
|
458
|
-
//# sourceMappingURL=index.mjs.map
|
|
348
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { i as Message } from "../../shapes-B0roKIQa.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/features/loader/resolve.d.ts
|
|
4
|
+
declare function resolveConfig(name?: string): {
|
|
5
|
+
sourceLocale: string;
|
|
6
|
+
locales: [string, ...string[]];
|
|
7
|
+
buckets: {
|
|
8
|
+
match: (id: string) => boolean;
|
|
9
|
+
include: string[];
|
|
10
|
+
output: `${string}{locale}${string}.{extension}`;
|
|
11
|
+
formatter: {
|
|
12
|
+
extension: `.${string}`;
|
|
13
|
+
parse: (content: string, context: {
|
|
14
|
+
locale: string;
|
|
15
|
+
}) => Message[];
|
|
16
|
+
stringify: (messages: Message[], context: {
|
|
17
|
+
locale: string;
|
|
18
|
+
}) => string;
|
|
19
|
+
};
|
|
20
|
+
transformer: {
|
|
21
|
+
match: (id: string) => boolean;
|
|
22
|
+
extract: (code: string, id: string) => {
|
|
23
|
+
message: string;
|
|
24
|
+
comments: string[];
|
|
25
|
+
references: string[];
|
|
26
|
+
translation?: string | undefined;
|
|
27
|
+
id?: string | undefined;
|
|
28
|
+
context?: string | undefined;
|
|
29
|
+
}[];
|
|
30
|
+
transform: (code: string, id: string) => string;
|
|
31
|
+
};
|
|
32
|
+
exclude?: string[] | undefined;
|
|
33
|
+
}[];
|
|
34
|
+
fallbackLocales?: Record<string, string[]> | undefined;
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { resolveConfig };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/features/messages/identifier.d.ts
|
|
2
|
+
declare const AUTO_INCREMENT_IDENTIFIER: unique symbol;
|
|
3
|
+
declare function assignSequenceIdentifiers(message: Message, sequence?: {
|
|
4
|
+
current: number;
|
|
5
|
+
}): void;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/features/messages/types.d.ts
|
|
8
|
+
declare abstract class Base {
|
|
9
|
+
toICUString(this: Message): string;
|
|
10
|
+
toHashString(this: Message): string;
|
|
11
|
+
}
|
|
12
|
+
declare class LiteralMessage extends Base {
|
|
13
|
+
readonly text: string;
|
|
14
|
+
constructor(text: string);
|
|
15
|
+
}
|
|
16
|
+
declare class ArgumentMessage extends Base {
|
|
17
|
+
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
18
|
+
readonly expression: any;
|
|
19
|
+
constructor(identifier: string | typeof AUTO_INCREMENT_IDENTIFIER, expression: any);
|
|
20
|
+
}
|
|
21
|
+
declare class ElementMessage extends Base {
|
|
22
|
+
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
23
|
+
readonly children: Message[];
|
|
24
|
+
readonly expression: any;
|
|
25
|
+
constructor(identifier: string | typeof AUTO_INCREMENT_IDENTIFIER, children: Message[], expression: any);
|
|
26
|
+
}
|
|
27
|
+
declare class ChoiceMessage extends Base {
|
|
28
|
+
readonly kind: string;
|
|
29
|
+
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
30
|
+
readonly branches: {
|
|
31
|
+
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
32
|
+
readonly value: Message;
|
|
33
|
+
}[];
|
|
34
|
+
readonly expression: any;
|
|
35
|
+
constructor(kind: string, identifier: string | typeof AUTO_INCREMENT_IDENTIFIER, branches: {
|
|
36
|
+
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
37
|
+
readonly value: Message;
|
|
38
|
+
}[], expression: any);
|
|
39
|
+
}
|
|
40
|
+
declare class CompositeMessage extends Base {
|
|
41
|
+
readonly descriptor: {
|
|
42
|
+
id?: string;
|
|
43
|
+
context?: string;
|
|
44
|
+
};
|
|
45
|
+
readonly comments: string[];
|
|
46
|
+
readonly references: string[];
|
|
47
|
+
readonly children: Message[];
|
|
48
|
+
readonly accessor: any;
|
|
49
|
+
constructor(descriptor: {
|
|
50
|
+
id?: string;
|
|
51
|
+
context?: string;
|
|
52
|
+
}, comments: string[], references: string[], children: Message[], accessor: any);
|
|
53
|
+
}
|
|
54
|
+
type Message = LiteralMessage | ArgumentMessage | ElementMessage | ChoiceMessage | CompositeMessage;
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/features/messages/convert.d.ts
|
|
57
|
+
declare function convertMessageToIcu(message: Message): string;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/features/messages/hash.d.ts
|
|
60
|
+
declare function generateHash(input: string, context?: string): string;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, Message, assignSequenceIdentifiers, convertMessageToIcu, generateHash };
|