@saykit/config 0.1.0 → 0.3.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/README.md +22 -0
- package/dist/commands/index.cjs +36 -91
- package/dist/commands/index.mjs +36 -91
- package/dist/features/catalogue/index.cjs +60 -0
- package/dist/features/catalogue/index.d.cts +59 -0
- package/dist/features/catalogue/index.d.mts +59 -0
- package/dist/features/catalogue/index.mjs +51 -0
- package/dist/features/loader/index.cjs +1 -1
- package/dist/features/loader/index.d.cts +1 -1
- package/dist/features/loader/index.d.mts +1 -1
- package/dist/features/loader/index.mjs +1 -1
- package/dist/features/messages/index.cjs +3 -2
- package/dist/features/messages/index.d.cts +2 -1
- package/dist/features/messages/index.d.mts +2 -1
- package/dist/features/messages/index.mjs +3 -2
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +7 -0
- package/dist/{shapes-CJyTfZXd.d.cts → shapes-Ba3xS10n.d.cts} +1 -0
- package/dist/{shapes-DETrtvZf.d.mts → shapes-NPfwlInG.d.mts} +1 -0
- package/dist/storage-3BTY8yws.cjs +97 -0
- package/dist/storage-C9nzxPtZ.mjs +67 -0
- package/package.json +12 -4
- /package/dist/{hash-CDfMT76E.cjs → hash-8It7TJpB.cjs} +0 -0
- /package/dist/{hash-Cs0dRdGf.mjs → hash-DyC8GzMa.mjs} +0 -0
- /package/dist/{loader-DnWt_9U2.cjs → loader-B25O-vF6.cjs} +0 -0
- /package/dist/{loader-CGuahnrc.mjs → loader-C2eolE_1.mjs} +0 -0
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> Configuration schema and CLI for [SayKit](https://saykit.js.org).
|
|
4
4
|
|
|
5
|
+
[](https://codecov.io/gh/k0d13/saykit?flags%5B0%5D=config)
|
|
6
|
+
|
|
5
7
|
Provides the `defineConfig` helper, the Zod-validated schema for `saykit.config.ts`, and the `saykit` CLI used to extract messages from your source.
|
|
6
8
|
|
|
7
9
|
## Install
|
|
@@ -19,6 +21,12 @@ import js from '@saykit/transform-js';
|
|
|
19
21
|
|
|
20
22
|
export default defineConfig({
|
|
21
23
|
locales: ['en', 'fr'],
|
|
24
|
+
// Optional per-locale fallback chains, most specific first. The source locale
|
|
25
|
+
// (the first entry in `locales`) is always the final fallback.
|
|
26
|
+
fallbackLocales: {
|
|
27
|
+
'en-NZ': ['en-GB'],
|
|
28
|
+
'es-MX': 'es',
|
|
29
|
+
},
|
|
22
30
|
buckets: [
|
|
23
31
|
{
|
|
24
32
|
include: ['src/**/*.ts'],
|
|
@@ -35,8 +43,22 @@ export default defineConfig({
|
|
|
35
43
|
```sh
|
|
36
44
|
saykit extract # extract messages once
|
|
37
45
|
saykit extract --watch # extract and watch for changes
|
|
46
|
+
saykit clean # reconcile other locales against the source
|
|
38
47
|
```
|
|
39
48
|
|
|
49
|
+
Extraction only writes the **source** locale (the first entry in `locales`). Locales that don't
|
|
50
|
+
have a file yet are bootstrapped with an empty, header-only catalogue so a TMS can register them;
|
|
51
|
+
existing translation files are left untouched. Translated content is owned by your TMS.
|
|
52
|
+
|
|
53
|
+
At load time (via the unplugin or Babel plugin), each locale module is filled from its fallback
|
|
54
|
+
chain, so an untranslated key resolves to a fallback locale and ultimately the source string — the
|
|
55
|
+
runtime still loads a single locale.
|
|
56
|
+
|
|
57
|
+
`saykit clean` reconciles every non-source locale against the current source catalogue: it adds
|
|
58
|
+
missing keys (untranslated), drops entries that no longer exist in the source, and preserves any
|
|
59
|
+
existing translations. Use it when you want to propagate source changes into locale files yourself
|
|
60
|
+
rather than leaving it to your TMS.
|
|
61
|
+
|
|
40
62
|
## Documentation
|
|
41
63
|
|
|
42
64
|
Full configuration guide and CLI reference at [saykit.js.org](https://saykit.js.org).
|
package/dist/commands/index.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
require("../index.cjs");
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const require_storage = require("../storage-3BTY8yws.cjs");
|
|
4
|
+
const require_loader = require("../loader-B25O-vF6.cjs");
|
|
5
5
|
let _commander_js_extra_typings = require("@commander-js/extra-typings");
|
|
6
|
+
let node_fs_promises = require("node:fs/promises");
|
|
6
7
|
let node_path = require("node:path");
|
|
7
8
|
let node_async_hooks = require("node:async_hooks");
|
|
8
|
-
let node_fs_promises = require("node:fs/promises");
|
|
9
9
|
//#region src/features/logger.ts
|
|
10
10
|
const RESET = "\x1B[0m";
|
|
11
11
|
const DIM = "\x1B[2m";
|
|
@@ -45,6 +45,23 @@ var Logger = class {
|
|
|
45
45
|
};
|
|
46
46
|
new node_async_hooks.AsyncLocalStorage({ defaultValue: new Logger() });
|
|
47
47
|
//#endregion
|
|
48
|
+
//#region src/commands/clean.ts
|
|
49
|
+
var clean_default = new _commander_js_extra_typings.Command("clean").description("Reconcile non-source locale files against the source locale").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
|
|
50
|
+
const config = require_loader.resolveConfig();
|
|
51
|
+
const logger = new Logger(options);
|
|
52
|
+
logger.header("🧹 Cleaning Locales");
|
|
53
|
+
const [sourceLocale, ...otherLocales] = config.locales;
|
|
54
|
+
for (const bucket of config.buckets) {
|
|
55
|
+
const sourceMessages = await require_storage.readCatalogueMessages(bucket, sourceLocale);
|
|
56
|
+
logger.info(`Reconciling ${otherLocales.length} locale(s) against ${sourceLocale}`);
|
|
57
|
+
for (const locale of otherLocales) {
|
|
58
|
+
logger.step(`Reconciling ${locale}`);
|
|
59
|
+
await require_storage.writeCatalogueMessages(bucket, locale, require_storage.reconcileLocaleMessages(await require_storage.readCatalogueMessages(bucket, locale), sourceMessages));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
logger.success("Locales cleaned");
|
|
63
|
+
});
|
|
64
|
+
//#endregion
|
|
48
65
|
//#region src/features/catalogue/extractor.ts
|
|
49
66
|
async function extractMessagesFromFile(path, bucket) {
|
|
50
67
|
const content = await (0, node_fs_promises.readFile)(path, "utf8").catch(() => "");
|
|
@@ -56,86 +73,6 @@ async function extractMessagesFromFile(path, bucket) {
|
|
|
56
73
|
}));
|
|
57
74
|
}
|
|
58
75
|
//#endregion
|
|
59
|
-
//#region src/features/catalogue/merge.ts
|
|
60
|
-
function mergeUnique(...items) {
|
|
61
|
-
return Array.from(new Set(items.flat()));
|
|
62
|
-
}
|
|
63
|
-
function getMessageKey(message) {
|
|
64
|
-
return message.id ?? require_hash.generateHash(message.message, message.context);
|
|
65
|
-
}
|
|
66
|
-
function mergeExtractedMessages(messages) {
|
|
67
|
-
const mergedMessages = messages.reduce((map, message) => {
|
|
68
|
-
const key = getMessageKey(message);
|
|
69
|
-
const existing = map.get(key) ?? message;
|
|
70
|
-
map.set(key, {
|
|
71
|
-
...existing,
|
|
72
|
-
comments: mergeUnique(...existing.comments, ...message.comments),
|
|
73
|
-
references: mergeUnique(...existing.references, ...message.references)
|
|
74
|
-
});
|
|
75
|
-
return map;
|
|
76
|
-
}, /* @__PURE__ */ new Map());
|
|
77
|
-
return Array.from(mergedMessages.values());
|
|
78
|
-
}
|
|
79
|
-
function reconcileLocaleMessages(existingMessages, nextMessages) {
|
|
80
|
-
const existingMessagesByKey = existingMessages.reduce((map, message) => {
|
|
81
|
-
map.set(getMessageKey(message), message);
|
|
82
|
-
return map;
|
|
83
|
-
}, /* @__PURE__ */ new Map());
|
|
84
|
-
const reconciledMessages = nextMessages.reduce((map, message) => {
|
|
85
|
-
const key = getMessageKey(message);
|
|
86
|
-
const existingMessage = existingMessagesByKey.get(key);
|
|
87
|
-
map.set(key, {
|
|
88
|
-
message: message.message,
|
|
89
|
-
translation: void 0,
|
|
90
|
-
...existingMessage,
|
|
91
|
-
id: message.id,
|
|
92
|
-
context: message.context,
|
|
93
|
-
comments: message.comments,
|
|
94
|
-
references: message.references
|
|
95
|
-
});
|
|
96
|
-
return map;
|
|
97
|
-
}, /* @__PURE__ */ new Map());
|
|
98
|
-
return Array.from(reconciledMessages.values());
|
|
99
|
-
}
|
|
100
|
-
//#endregion
|
|
101
|
-
//#region src/features/catalogue/path.ts
|
|
102
|
-
function expandBucketOutputPath(bucket, locale, extension = bucket.formatter.extension) {
|
|
103
|
-
return (0, node_path.resolve)(bucket.output.replaceAll("{locale}", locale).replaceAll("{extension}", extension.slice(1)));
|
|
104
|
-
}
|
|
105
|
-
function expandBucketOutputIgnoreDirectory(bucket) {
|
|
106
|
-
const [prefix] = bucket.output.split("{locale}");
|
|
107
|
-
return (0, node_path.resolve)(prefix || ".");
|
|
108
|
-
}
|
|
109
|
-
//#endregion
|
|
110
|
-
//#region src/features/catalogue/storage.ts
|
|
111
|
-
const DECLARATION_CONTENT = `
|
|
112
|
-
declare const translations: Record<string, string>;
|
|
113
|
-
export default translations;
|
|
114
|
-
`.trim();
|
|
115
|
-
async function readCatalogueMessages(bucket, locale, path = expandBucketOutputPath(bucket, locale)) {
|
|
116
|
-
const content = await (0, node_fs_promises.readFile)(path, "utf8").catch(() => "");
|
|
117
|
-
if (!content) return [];
|
|
118
|
-
return bucket.formatter.parse(content);
|
|
119
|
-
}
|
|
120
|
-
async function writeCatalogueMessages(bucket, locale, messages, path = expandBucketOutputPath(bucket, locale)) {
|
|
121
|
-
const existingContent = await (0, node_fs_promises.readFile)(path, "utf8").catch(() => void 0);
|
|
122
|
-
const catalogueContent = bucket.formatter.stringify(messages, {
|
|
123
|
-
locale,
|
|
124
|
-
existingContent
|
|
125
|
-
});
|
|
126
|
-
const declarationPath = `${path}.d.ts`;
|
|
127
|
-
const ignoreDirectory = expandBucketOutputIgnoreDirectory(bucket);
|
|
128
|
-
const ignorePath = (0, node_path.join)(ignoreDirectory, ".gitignore");
|
|
129
|
-
const ignoreContent = `.gitignore\n*.${bucket.formatter.extension.slice(1)}.d.ts`;
|
|
130
|
-
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
|
|
131
|
-
await (0, node_fs_promises.mkdir)(ignoreDirectory, { recursive: true });
|
|
132
|
-
await Promise.all([
|
|
133
|
-
(0, node_fs_promises.writeFile)(path, catalogueContent),
|
|
134
|
-
(0, node_fs_promises.writeFile)(declarationPath, DECLARATION_CONTENT),
|
|
135
|
-
(0, node_fs_promises.writeFile)(ignorePath, ignoreContent)
|
|
136
|
-
]);
|
|
137
|
-
}
|
|
138
|
-
//#endregion
|
|
139
76
|
//#region src/features/watch.ts
|
|
140
77
|
/**
|
|
141
78
|
* Expand a buckets include and exclude patterns into a flat list of file paths.
|
|
@@ -195,6 +132,9 @@ var BucketWorker = class {
|
|
|
195
132
|
};
|
|
196
133
|
//#endregion
|
|
197
134
|
//#region src/features/workers/extract-worker.ts
|
|
135
|
+
function exists(path) {
|
|
136
|
+
return (0, node_fs_promises.access)(path).then(() => true, () => false);
|
|
137
|
+
}
|
|
198
138
|
var BucketExtractWorker = class extends BucketWorker {
|
|
199
139
|
#indexedMessagesByPath = /* @__PURE__ */ new Map();
|
|
200
140
|
get #indexedMessages() {
|
|
@@ -224,13 +164,18 @@ var BucketExtractWorker = class extends BucketWorker {
|
|
|
224
164
|
this.logger.info(`Total extracted messages: ${this.#indexedMessages.length}`);
|
|
225
165
|
}
|
|
226
166
|
async write() {
|
|
227
|
-
const mergedMessages = mergeExtractedMessages(this.#indexedMessages);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
await
|
|
167
|
+
const mergedMessages = require_storage.mergeExtractedMessages(this.#indexedMessages);
|
|
168
|
+
const [sourceLocale, ...otherLocales] = this.config.locales;
|
|
169
|
+
this.logger.info(`Writing ${mergedMessages.length} messages to ${sourceLocale}`);
|
|
170
|
+
this.logger.step(`Writing locale file for ${sourceLocale} to disk`);
|
|
171
|
+
await require_storage.writeCatalogueMessages(this.bucket, sourceLocale, mergedMessages);
|
|
172
|
+
for (const locale of otherLocales) {
|
|
173
|
+
if (await exists(require_storage.expandBucketOutputPath(this.bucket, locale))) {
|
|
174
|
+
this.logger.step(`Skipping existing locale file for ${locale}`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
this.logger.step(`Creating empty locale file for ${locale}`);
|
|
178
|
+
await require_storage.writeCatalogueMessages(this.bucket, locale, []);
|
|
234
179
|
}
|
|
235
180
|
this.logger.success(`Extraction complete for bucket: ${this.bucket.include}`);
|
|
236
181
|
}
|
|
@@ -260,5 +205,5 @@ var extract_default = new _commander_js_extra_typings.Command("extract").descrip
|
|
|
260
205
|
});
|
|
261
206
|
//#endregion
|
|
262
207
|
//#region src/commands/index.ts
|
|
263
|
-
_commander_js_extra_typings.program.name("saykit").helpOption("-h, --help", "Display help for command").helpCommand("help [command]", "Display help for command").addCommand(extract_default).parse();
|
|
208
|
+
_commander_js_extra_typings.program.name("saykit").helpOption("-h, --help", "Display help for command").helpCommand("help [command]", "Display help for command").addCommand(extract_default).addCommand(clean_default).parse();
|
|
264
209
|
//#endregion
|
package/dist/commands/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as
|
|
3
|
-
import { t as
|
|
2
|
+
import { a as reconcileLocaleMessages, i as mergeExtractedMessages, n as writeCatalogueMessages, r as expandBucketOutputPath, t as readCatalogueMessages } from "../storage-C9nzxPtZ.mjs";
|
|
3
|
+
import { t as resolveConfig } from "../loader-C2eolE_1.mjs";
|
|
4
4
|
import { Command, program } from "@commander-js/extra-typings";
|
|
5
|
-
import {
|
|
5
|
+
import { access, glob, readFile, watch } from "node:fs/promises";
|
|
6
|
+
import { join, relative } from "node:path";
|
|
6
7
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
|
-
import { glob, mkdir, readFile, watch, writeFile } from "node:fs/promises";
|
|
8
8
|
//#region src/features/logger.ts
|
|
9
9
|
const RESET = "\x1B[0m";
|
|
10
10
|
const DIM = "\x1B[2m";
|
|
@@ -44,6 +44,23 @@ var Logger = class {
|
|
|
44
44
|
};
|
|
45
45
|
new AsyncLocalStorage({ defaultValue: new Logger() });
|
|
46
46
|
//#endregion
|
|
47
|
+
//#region src/commands/clean.ts
|
|
48
|
+
var clean_default = new Command("clean").description("Reconcile non-source locale files against the source locale").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
|
|
49
|
+
const config = resolveConfig();
|
|
50
|
+
const logger = new Logger(options);
|
|
51
|
+
logger.header("🧹 Cleaning Locales");
|
|
52
|
+
const [sourceLocale, ...otherLocales] = config.locales;
|
|
53
|
+
for (const bucket of config.buckets) {
|
|
54
|
+
const sourceMessages = await readCatalogueMessages(bucket, sourceLocale);
|
|
55
|
+
logger.info(`Reconciling ${otherLocales.length} locale(s) against ${sourceLocale}`);
|
|
56
|
+
for (const locale of otherLocales) {
|
|
57
|
+
logger.step(`Reconciling ${locale}`);
|
|
58
|
+
await writeCatalogueMessages(bucket, locale, reconcileLocaleMessages(await readCatalogueMessages(bucket, locale), sourceMessages));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
logger.success("Locales cleaned");
|
|
62
|
+
});
|
|
63
|
+
//#endregion
|
|
47
64
|
//#region src/features/catalogue/extractor.ts
|
|
48
65
|
async function extractMessagesFromFile(path, bucket) {
|
|
49
66
|
const content = await readFile(path, "utf8").catch(() => "");
|
|
@@ -55,86 +72,6 @@ async function extractMessagesFromFile(path, bucket) {
|
|
|
55
72
|
}));
|
|
56
73
|
}
|
|
57
74
|
//#endregion
|
|
58
|
-
//#region src/features/catalogue/merge.ts
|
|
59
|
-
function mergeUnique(...items) {
|
|
60
|
-
return Array.from(new Set(items.flat()));
|
|
61
|
-
}
|
|
62
|
-
function getMessageKey(message) {
|
|
63
|
-
return message.id ?? generateHash(message.message, message.context);
|
|
64
|
-
}
|
|
65
|
-
function mergeExtractedMessages(messages) {
|
|
66
|
-
const mergedMessages = messages.reduce((map, message) => {
|
|
67
|
-
const key = getMessageKey(message);
|
|
68
|
-
const existing = map.get(key) ?? message;
|
|
69
|
-
map.set(key, {
|
|
70
|
-
...existing,
|
|
71
|
-
comments: mergeUnique(...existing.comments, ...message.comments),
|
|
72
|
-
references: mergeUnique(...existing.references, ...message.references)
|
|
73
|
-
});
|
|
74
|
-
return map;
|
|
75
|
-
}, /* @__PURE__ */ new Map());
|
|
76
|
-
return Array.from(mergedMessages.values());
|
|
77
|
-
}
|
|
78
|
-
function reconcileLocaleMessages(existingMessages, nextMessages) {
|
|
79
|
-
const existingMessagesByKey = existingMessages.reduce((map, message) => {
|
|
80
|
-
map.set(getMessageKey(message), message);
|
|
81
|
-
return map;
|
|
82
|
-
}, /* @__PURE__ */ new Map());
|
|
83
|
-
const reconciledMessages = nextMessages.reduce((map, message) => {
|
|
84
|
-
const key = getMessageKey(message);
|
|
85
|
-
const existingMessage = existingMessagesByKey.get(key);
|
|
86
|
-
map.set(key, {
|
|
87
|
-
message: message.message,
|
|
88
|
-
translation: void 0,
|
|
89
|
-
...existingMessage,
|
|
90
|
-
id: message.id,
|
|
91
|
-
context: message.context,
|
|
92
|
-
comments: message.comments,
|
|
93
|
-
references: message.references
|
|
94
|
-
});
|
|
95
|
-
return map;
|
|
96
|
-
}, /* @__PURE__ */ new Map());
|
|
97
|
-
return Array.from(reconciledMessages.values());
|
|
98
|
-
}
|
|
99
|
-
//#endregion
|
|
100
|
-
//#region src/features/catalogue/path.ts
|
|
101
|
-
function expandBucketOutputPath(bucket, locale, extension = bucket.formatter.extension) {
|
|
102
|
-
return resolve(bucket.output.replaceAll("{locale}", locale).replaceAll("{extension}", extension.slice(1)));
|
|
103
|
-
}
|
|
104
|
-
function expandBucketOutputIgnoreDirectory(bucket) {
|
|
105
|
-
const [prefix] = bucket.output.split("{locale}");
|
|
106
|
-
return resolve(prefix || ".");
|
|
107
|
-
}
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region src/features/catalogue/storage.ts
|
|
110
|
-
const DECLARATION_CONTENT = `
|
|
111
|
-
declare const translations: Record<string, string>;
|
|
112
|
-
export default translations;
|
|
113
|
-
`.trim();
|
|
114
|
-
async function readCatalogueMessages(bucket, locale, path = expandBucketOutputPath(bucket, locale)) {
|
|
115
|
-
const content = await readFile(path, "utf8").catch(() => "");
|
|
116
|
-
if (!content) return [];
|
|
117
|
-
return bucket.formatter.parse(content);
|
|
118
|
-
}
|
|
119
|
-
async function writeCatalogueMessages(bucket, locale, messages, path = expandBucketOutputPath(bucket, locale)) {
|
|
120
|
-
const existingContent = await readFile(path, "utf8").catch(() => void 0);
|
|
121
|
-
const catalogueContent = bucket.formatter.stringify(messages, {
|
|
122
|
-
locale,
|
|
123
|
-
existingContent
|
|
124
|
-
});
|
|
125
|
-
const declarationPath = `${path}.d.ts`;
|
|
126
|
-
const ignoreDirectory = expandBucketOutputIgnoreDirectory(bucket);
|
|
127
|
-
const ignorePath = join(ignoreDirectory, ".gitignore");
|
|
128
|
-
const ignoreContent = `.gitignore\n*.${bucket.formatter.extension.slice(1)}.d.ts`;
|
|
129
|
-
await mkdir(dirname(path), { recursive: true });
|
|
130
|
-
await mkdir(ignoreDirectory, { recursive: true });
|
|
131
|
-
await Promise.all([
|
|
132
|
-
writeFile(path, catalogueContent),
|
|
133
|
-
writeFile(declarationPath, DECLARATION_CONTENT),
|
|
134
|
-
writeFile(ignorePath, ignoreContent)
|
|
135
|
-
]);
|
|
136
|
-
}
|
|
137
|
-
//#endregion
|
|
138
75
|
//#region src/features/watch.ts
|
|
139
76
|
/**
|
|
140
77
|
* Expand a buckets include and exclude patterns into a flat list of file paths.
|
|
@@ -194,6 +131,9 @@ var BucketWorker = class {
|
|
|
194
131
|
};
|
|
195
132
|
//#endregion
|
|
196
133
|
//#region src/features/workers/extract-worker.ts
|
|
134
|
+
function exists(path) {
|
|
135
|
+
return access(path).then(() => true, () => false);
|
|
136
|
+
}
|
|
197
137
|
var BucketExtractWorker = class extends BucketWorker {
|
|
198
138
|
#indexedMessagesByPath = /* @__PURE__ */ new Map();
|
|
199
139
|
get #indexedMessages() {
|
|
@@ -224,12 +164,17 @@ var BucketExtractWorker = class extends BucketWorker {
|
|
|
224
164
|
}
|
|
225
165
|
async write() {
|
|
226
166
|
const mergedMessages = mergeExtractedMessages(this.#indexedMessages);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
await
|
|
167
|
+
const [sourceLocale, ...otherLocales] = this.config.locales;
|
|
168
|
+
this.logger.info(`Writing ${mergedMessages.length} messages to ${sourceLocale}`);
|
|
169
|
+
this.logger.step(`Writing locale file for ${sourceLocale} to disk`);
|
|
170
|
+
await writeCatalogueMessages(this.bucket, sourceLocale, mergedMessages);
|
|
171
|
+
for (const locale of otherLocales) {
|
|
172
|
+
if (await exists(expandBucketOutputPath(this.bucket, locale))) {
|
|
173
|
+
this.logger.step(`Skipping existing locale file for ${locale}`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
this.logger.step(`Creating empty locale file for ${locale}`);
|
|
177
|
+
await writeCatalogueMessages(this.bucket, locale, []);
|
|
233
178
|
}
|
|
234
179
|
this.logger.success(`Extraction complete for bucket: ${this.bucket.include}`);
|
|
235
180
|
}
|
|
@@ -259,6 +204,6 @@ var extract_default = new Command("extract").description("Extract messages from
|
|
|
259
204
|
});
|
|
260
205
|
//#endregion
|
|
261
206
|
//#region src/commands/index.ts
|
|
262
|
-
program.name("saykit").helpOption("-h, --help", "Display help for command").helpCommand("help [command]", "Display help for command").addCommand(extract_default).parse();
|
|
207
|
+
program.name("saykit").helpOption("-h, --help", "Display help for command").helpCommand("help [command]", "Display help for command").addCommand(extract_default).addCommand(clean_default).parse();
|
|
263
208
|
//#endregion
|
|
264
209
|
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("../../index.cjs");
|
|
3
|
+
const require_hash = require("../../hash-8It7TJpB.cjs");
|
|
4
|
+
const require_storage = require("../../storage-3BTY8yws.cjs");
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
//#region src/features/catalogue/record.ts
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the fallback chain for a locale, most specific first. The source
|
|
9
|
+
* locale (the first configured locale) is always the final fallback, so an
|
|
10
|
+
* untranslated key ultimately resolves to the source string.
|
|
11
|
+
*/
|
|
12
|
+
function resolveFallbackChain(config, locale) {
|
|
13
|
+
const source = config.locales[0];
|
|
14
|
+
const configured = config.fallbackLocales?.[locale];
|
|
15
|
+
const fallbacks = configured ? Array.isArray(configured) ? configured : [configured] : [];
|
|
16
|
+
return Array.from(new Set([
|
|
17
|
+
locale,
|
|
18
|
+
...fallbacks,
|
|
19
|
+
source
|
|
20
|
+
]));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the catalogue files that contribute to a locale module, most specific
|
|
24
|
+
* first. When the path does not map to a configured locale it is loaded on its
|
|
25
|
+
* own.
|
|
26
|
+
*/
|
|
27
|
+
function resolveCatalogueSources(config, bucket, path) {
|
|
28
|
+
const resolved = (0, node_path.resolve)(path);
|
|
29
|
+
const locale = config.locales.find((l) => require_storage.expandBucketOutputPath(bucket, l) === resolved);
|
|
30
|
+
return {
|
|
31
|
+
locale,
|
|
32
|
+
sources: locale ? resolveFallbackChain(config, locale).map((l) => require_storage.expandBucketOutputPath(bucket, l)) : [resolved]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Assemble a `{ id: string }` record from catalogue file contents. `contents`
|
|
37
|
+
* must be aligned with the `sources` returned by {@link resolveCatalogueSources}
|
|
38
|
+
* (most specific first): more specific locales override their fallbacks, and any
|
|
39
|
+
* key still untranslated falls back to its source message.
|
|
40
|
+
*/
|
|
41
|
+
function assembleCatalogueRecord(bucket, contents) {
|
|
42
|
+
const record = {};
|
|
43
|
+
for (const content of [...contents].reverse()) {
|
|
44
|
+
if (!content) continue;
|
|
45
|
+
for (const message of bucket.formatter.parse(content)) {
|
|
46
|
+
const key = message.id || require_hash.generateHash(message.message, message.context);
|
|
47
|
+
record[key] = message.translation || message.message;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return record;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
exports.assembleCatalogueRecord = assembleCatalogueRecord;
|
|
54
|
+
exports.expandBucketOutputPath = require_storage.expandBucketOutputPath;
|
|
55
|
+
exports.mergeExtractedMessages = require_storage.mergeExtractedMessages;
|
|
56
|
+
exports.readCatalogueMessages = require_storage.readCatalogueMessages;
|
|
57
|
+
exports.reconcileLocaleMessages = require_storage.reconcileLocaleMessages;
|
|
58
|
+
exports.resolveCatalogueSources = resolveCatalogueSources;
|
|
59
|
+
exports.resolveFallbackChain = resolveFallbackChain;
|
|
60
|
+
exports.writeCatalogueMessages = require_storage.writeCatalogueMessages;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { i as Message, n as Config, t as Bucket } from "../../shapes-Ba3xS10n.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/features/catalogue/merge.d.ts
|
|
4
|
+
declare function mergeExtractedMessages(messages: Message[]): {
|
|
5
|
+
message: string;
|
|
6
|
+
comments: string[];
|
|
7
|
+
references: string[];
|
|
8
|
+
translation?: string | undefined;
|
|
9
|
+
id?: string | undefined;
|
|
10
|
+
context?: string | undefined;
|
|
11
|
+
}[];
|
|
12
|
+
declare function reconcileLocaleMessages(existingMessages: Message[], nextMessages: Message[]): {
|
|
13
|
+
message: string;
|
|
14
|
+
comments: string[];
|
|
15
|
+
references: string[];
|
|
16
|
+
translation?: string | undefined;
|
|
17
|
+
id?: string | undefined;
|
|
18
|
+
context?: string | undefined;
|
|
19
|
+
}[];
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/features/catalogue/path.d.ts
|
|
22
|
+
declare function expandBucketOutputPath(bucket: Bucket, locale: string, extension?: `.${string}`): string;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/features/catalogue/record.d.ts
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the fallback chain for a locale, most specific first. The source
|
|
27
|
+
* locale (the first configured locale) is always the final fallback, so an
|
|
28
|
+
* untranslated key ultimately resolves to the source string.
|
|
29
|
+
*/
|
|
30
|
+
declare function resolveFallbackChain(config: Config, locale: string): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the catalogue files that contribute to a locale module, most specific
|
|
33
|
+
* first. When the path does not map to a configured locale it is loaded on its
|
|
34
|
+
* own.
|
|
35
|
+
*/
|
|
36
|
+
declare function resolveCatalogueSources(config: Config, bucket: Bucket, path: string): {
|
|
37
|
+
locale: string | undefined;
|
|
38
|
+
sources: string[];
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Assemble a `{ id: string }` record from catalogue file contents. `contents`
|
|
42
|
+
* must be aligned with the `sources` returned by {@link resolveCatalogueSources}
|
|
43
|
+
* (most specific first): more specific locales override their fallbacks, and any
|
|
44
|
+
* key still untranslated falls back to its source message.
|
|
45
|
+
*/
|
|
46
|
+
declare function assembleCatalogueRecord(bucket: Bucket, contents: string[]): Record<string, string>;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/features/catalogue/storage.d.ts
|
|
49
|
+
declare function readCatalogueMessages(bucket: Bucket, locale: string, path?: string): Promise<{
|
|
50
|
+
message: string;
|
|
51
|
+
comments: string[];
|
|
52
|
+
references: string[];
|
|
53
|
+
translation?: string | undefined;
|
|
54
|
+
id?: string | undefined;
|
|
55
|
+
context?: string | undefined;
|
|
56
|
+
}[]>;
|
|
57
|
+
declare function writeCatalogueMessages(bucket: Bucket, locale: string, messages: Message[], path?: string): Promise<void>;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { assembleCatalogueRecord, expandBucketOutputPath, mergeExtractedMessages, readCatalogueMessages, reconcileLocaleMessages, resolveCatalogueSources, resolveFallbackChain, writeCatalogueMessages };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { i as Message, n as Config, t as Bucket } from "../../shapes-NPfwlInG.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/features/catalogue/merge.d.ts
|
|
4
|
+
declare function mergeExtractedMessages(messages: Message[]): {
|
|
5
|
+
message: string;
|
|
6
|
+
comments: string[];
|
|
7
|
+
references: string[];
|
|
8
|
+
translation?: string | undefined;
|
|
9
|
+
id?: string | undefined;
|
|
10
|
+
context?: string | undefined;
|
|
11
|
+
}[];
|
|
12
|
+
declare function reconcileLocaleMessages(existingMessages: Message[], nextMessages: Message[]): {
|
|
13
|
+
message: string;
|
|
14
|
+
comments: string[];
|
|
15
|
+
references: string[];
|
|
16
|
+
translation?: string | undefined;
|
|
17
|
+
id?: string | undefined;
|
|
18
|
+
context?: string | undefined;
|
|
19
|
+
}[];
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/features/catalogue/path.d.ts
|
|
22
|
+
declare function expandBucketOutputPath(bucket: Bucket, locale: string, extension?: `.${string}`): string;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/features/catalogue/record.d.ts
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the fallback chain for a locale, most specific first. The source
|
|
27
|
+
* locale (the first configured locale) is always the final fallback, so an
|
|
28
|
+
* untranslated key ultimately resolves to the source string.
|
|
29
|
+
*/
|
|
30
|
+
declare function resolveFallbackChain(config: Config, locale: string): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the catalogue files that contribute to a locale module, most specific
|
|
33
|
+
* first. When the path does not map to a configured locale it is loaded on its
|
|
34
|
+
* own.
|
|
35
|
+
*/
|
|
36
|
+
declare function resolveCatalogueSources(config: Config, bucket: Bucket, path: string): {
|
|
37
|
+
locale: string | undefined;
|
|
38
|
+
sources: string[];
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Assemble a `{ id: string }` record from catalogue file contents. `contents`
|
|
42
|
+
* must be aligned with the `sources` returned by {@link resolveCatalogueSources}
|
|
43
|
+
* (most specific first): more specific locales override their fallbacks, and any
|
|
44
|
+
* key still untranslated falls back to its source message.
|
|
45
|
+
*/
|
|
46
|
+
declare function assembleCatalogueRecord(bucket: Bucket, contents: string[]): Record<string, string>;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/features/catalogue/storage.d.ts
|
|
49
|
+
declare function readCatalogueMessages(bucket: Bucket, locale: string, path?: string): Promise<{
|
|
50
|
+
message: string;
|
|
51
|
+
comments: string[];
|
|
52
|
+
references: string[];
|
|
53
|
+
translation?: string | undefined;
|
|
54
|
+
id?: string | undefined;
|
|
55
|
+
context?: string | undefined;
|
|
56
|
+
}[]>;
|
|
57
|
+
declare function writeCatalogueMessages(bucket: Bucket, locale: string, messages: Message[], path?: string): Promise<void>;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { assembleCatalogueRecord, expandBucketOutputPath, mergeExtractedMessages, readCatalogueMessages, reconcileLocaleMessages, resolveCatalogueSources, resolveFallbackChain, writeCatalogueMessages };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { t as generateHash } from "../../hash-DyC8GzMa.mjs";
|
|
2
|
+
import { a as reconcileLocaleMessages, i as mergeExtractedMessages, n as writeCatalogueMessages, r as expandBucketOutputPath, t as readCatalogueMessages } from "../../storage-C9nzxPtZ.mjs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
//#region src/features/catalogue/record.ts
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the fallback chain for a locale, most specific first. The source
|
|
7
|
+
* locale (the first configured locale) is always the final fallback, so an
|
|
8
|
+
* untranslated key ultimately resolves to the source string.
|
|
9
|
+
*/
|
|
10
|
+
function resolveFallbackChain(config, locale) {
|
|
11
|
+
const source = config.locales[0];
|
|
12
|
+
const configured = config.fallbackLocales?.[locale];
|
|
13
|
+
const fallbacks = configured ? Array.isArray(configured) ? configured : [configured] : [];
|
|
14
|
+
return Array.from(new Set([
|
|
15
|
+
locale,
|
|
16
|
+
...fallbacks,
|
|
17
|
+
source
|
|
18
|
+
]));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the catalogue files that contribute to a locale module, most specific
|
|
22
|
+
* first. When the path does not map to a configured locale it is loaded on its
|
|
23
|
+
* own.
|
|
24
|
+
*/
|
|
25
|
+
function resolveCatalogueSources(config, bucket, path) {
|
|
26
|
+
const resolved = resolve(path);
|
|
27
|
+
const locale = config.locales.find((l) => expandBucketOutputPath(bucket, l) === resolved);
|
|
28
|
+
return {
|
|
29
|
+
locale,
|
|
30
|
+
sources: locale ? resolveFallbackChain(config, locale).map((l) => expandBucketOutputPath(bucket, l)) : [resolved]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Assemble a `{ id: string }` record from catalogue file contents. `contents`
|
|
35
|
+
* must be aligned with the `sources` returned by {@link resolveCatalogueSources}
|
|
36
|
+
* (most specific first): more specific locales override their fallbacks, and any
|
|
37
|
+
* key still untranslated falls back to its source message.
|
|
38
|
+
*/
|
|
39
|
+
function assembleCatalogueRecord(bucket, contents) {
|
|
40
|
+
const record = {};
|
|
41
|
+
for (const content of [...contents].reverse()) {
|
|
42
|
+
if (!content) continue;
|
|
43
|
+
for (const message of bucket.formatter.parse(content)) {
|
|
44
|
+
const key = message.id || generateHash(message.message, message.context);
|
|
45
|
+
record[key] = message.translation || message.message;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return record;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { assembleCatalogueRecord, expandBucketOutputPath, mergeExtractedMessages, readCatalogueMessages, reconcileLocaleMessages, resolveCatalogueSources, resolveFallbackChain, writeCatalogueMessages };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as resolveConfig } from "../../loader-
|
|
1
|
+
import { t as resolveConfig } from "../../loader-C2eolE_1.mjs";
|
|
2
2
|
export { resolveConfig };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_hash = require("../../hash-
|
|
2
|
+
const require_hash = require("../../hash-8It7TJpB.cjs");
|
|
3
3
|
//#region src/features/messages/identifier.ts
|
|
4
4
|
const AUTO_INCREMENT_IDENTIFIER = Symbol("auto-increment");
|
|
5
5
|
function assignSequenceIdentifiers(message, sequence = { current: 0 }) {
|
|
@@ -54,13 +54,14 @@ var ChoiceMessage = class extends Base {
|
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
var CompositeMessage = class extends Base {
|
|
57
|
-
constructor(descriptor, comments, references, children, accessor) {
|
|
57
|
+
constructor(descriptor, comments, references, children, accessor, whitespace) {
|
|
58
58
|
super();
|
|
59
59
|
this.descriptor = descriptor;
|
|
60
60
|
this.comments = comments;
|
|
61
61
|
this.references = references;
|
|
62
62
|
this.children = children;
|
|
63
63
|
this.accessor = accessor;
|
|
64
|
+
this.whitespace = whitespace;
|
|
64
65
|
}
|
|
65
66
|
};
|
|
66
67
|
//#endregion
|
|
@@ -46,10 +46,11 @@ declare class CompositeMessage extends Base {
|
|
|
46
46
|
readonly references: string[];
|
|
47
47
|
readonly children: Message[];
|
|
48
48
|
readonly accessor: any;
|
|
49
|
+
readonly whitespace?: boolean | undefined;
|
|
49
50
|
constructor(descriptor: {
|
|
50
51
|
id?: string;
|
|
51
52
|
context?: string;
|
|
52
|
-
}, comments: string[], references: string[], children: Message[], accessor: any);
|
|
53
|
+
}, comments: string[], references: string[], children: Message[], accessor: any, whitespace?: boolean | undefined);
|
|
53
54
|
}
|
|
54
55
|
type Message = LiteralMessage | ArgumentMessage | ElementMessage | ChoiceMessage | CompositeMessage;
|
|
55
56
|
//#endregion
|
|
@@ -46,10 +46,11 @@ declare class CompositeMessage extends Base {
|
|
|
46
46
|
readonly references: string[];
|
|
47
47
|
readonly children: Message[];
|
|
48
48
|
readonly accessor: any;
|
|
49
|
+
readonly whitespace?: boolean | undefined;
|
|
49
50
|
constructor(descriptor: {
|
|
50
51
|
id?: string;
|
|
51
52
|
context?: string;
|
|
52
|
-
}, comments: string[], references: string[], children: Message[], accessor: any);
|
|
53
|
+
}, comments: string[], references: string[], children: Message[], accessor: any, whitespace?: boolean | undefined);
|
|
53
54
|
}
|
|
54
55
|
type Message = LiteralMessage | ArgumentMessage | ElementMessage | ChoiceMessage | CompositeMessage;
|
|
55
56
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as generateHash } from "../../hash-
|
|
1
|
+
import { t as generateHash } from "../../hash-DyC8GzMa.mjs";
|
|
2
2
|
//#region src/features/messages/identifier.ts
|
|
3
3
|
const AUTO_INCREMENT_IDENTIFIER = Symbol("auto-increment");
|
|
4
4
|
function assignSequenceIdentifiers(message, sequence = { current: 0 }) {
|
|
@@ -53,13 +53,14 @@ var ChoiceMessage = class extends Base {
|
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
var CompositeMessage = class extends Base {
|
|
56
|
-
constructor(descriptor, comments, references, children, accessor) {
|
|
56
|
+
constructor(descriptor, comments, references, children, accessor, whitespace) {
|
|
57
57
|
super();
|
|
58
58
|
this.descriptor = descriptor;
|
|
59
59
|
this.comments = comments;
|
|
60
60
|
this.references = references;
|
|
61
61
|
this.children = children;
|
|
62
62
|
this.accessor = accessor;
|
|
63
|
+
this.whitespace = whitespace;
|
|
63
64
|
}
|
|
64
65
|
};
|
|
65
66
|
//#endregion
|
package/dist/index.cjs
CHANGED
|
@@ -65,6 +65,13 @@ const Bucket = zod.object({
|
|
|
65
65
|
}));
|
|
66
66
|
const Config = zod.object({
|
|
67
67
|
locales: zod.tuple([zod.string()], zod.string()),
|
|
68
|
+
/**
|
|
69
|
+
* Per-locale fallback chains, most specific first, e.g.
|
|
70
|
+
* `{ 'en-NZ': ['en-GB'], 'es-MX': 'es' }`. The source locale (the first entry
|
|
71
|
+
* in {@link Config.locales}) is always appended as the final fallback, so an
|
|
72
|
+
* untranslated key ultimately resolves to the source string.
|
|
73
|
+
*/
|
|
74
|
+
fallbackLocales: zod.record(zod.string(), zod.string().or(zod.string().array())).optional(),
|
|
68
75
|
buckets: Bucket.array()
|
|
69
76
|
});
|
|
70
77
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as Transformer, i as Message, n as Config, r as Formatter, t as Bucket } from "./shapes-
|
|
1
|
+
import { a as Transformer, i as Message, n as Config, r as Formatter, t as Bucket } from "./shapes-Ba3xS10n.cjs";
|
|
2
2
|
import { input } from "zod";
|
|
3
3
|
import * as _$picomatch_lib_picomatch_js0 from "picomatch/lib/picomatch.js";
|
|
4
4
|
|
|
@@ -33,6 +33,7 @@ declare function defineConfig<C extends input<typeof Config>>(config: C): {
|
|
|
33
33
|
};
|
|
34
34
|
exclude?: string[] | undefined;
|
|
35
35
|
}[];
|
|
36
|
+
fallbackLocales?: Record<string, string | string[]> | undefined;
|
|
36
37
|
};
|
|
37
38
|
//#endregion
|
|
38
39
|
export { Bucket, Config, Formatter, Message, Transformer, defineConfig };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as Transformer, i as Message, n as Config, r as Formatter, t as Bucket } from "./shapes-
|
|
1
|
+
import { a as Transformer, i as Message, n as Config, r as Formatter, t as Bucket } from "./shapes-NPfwlInG.mjs";
|
|
2
2
|
import { input } from "zod";
|
|
3
3
|
import * as _$picomatch_lib_picomatch_js0 from "picomatch/lib/picomatch.js";
|
|
4
4
|
|
|
@@ -33,6 +33,7 @@ declare function defineConfig<C extends input<typeof Config>>(config: C): {
|
|
|
33
33
|
};
|
|
34
34
|
exclude?: string[] | undefined;
|
|
35
35
|
}[];
|
|
36
|
+
fallbackLocales?: Record<string, string | string[]> | undefined;
|
|
36
37
|
};
|
|
37
38
|
//#endregion
|
|
38
39
|
export { Bucket, Config, Formatter, Message, Transformer, defineConfig };
|
package/dist/index.mjs
CHANGED
|
@@ -40,6 +40,13 @@ const Bucket = z.object({
|
|
|
40
40
|
}));
|
|
41
41
|
const Config = z.object({
|
|
42
42
|
locales: z.tuple([z.string()], z.string()),
|
|
43
|
+
/**
|
|
44
|
+
* Per-locale fallback chains, most specific first, e.g.
|
|
45
|
+
* `{ 'en-NZ': ['en-GB'], 'es-MX': 'es' }`. The source locale (the first entry
|
|
46
|
+
* in {@link Config.locales}) is always appended as the final fallback, so an
|
|
47
|
+
* untranslated key ultimately resolves to the source string.
|
|
48
|
+
*/
|
|
49
|
+
fallbackLocales: z.record(z.string(), z.string().or(z.string().array())).optional(),
|
|
43
50
|
buckets: Bucket.array()
|
|
44
51
|
});
|
|
45
52
|
//#endregion
|
|
@@ -131,6 +131,7 @@ declare const Bucket: z.ZodPipe<z.ZodObject<{
|
|
|
131
131
|
type Bucket = z.infer<typeof Bucket>;
|
|
132
132
|
declare const Config: z.ZodObject<{
|
|
133
133
|
locales: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
134
|
+
fallbackLocales: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString>]>>>;
|
|
134
135
|
buckets: z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
135
136
|
include: z.ZodArray<z.ZodString>;
|
|
136
137
|
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -131,6 +131,7 @@ declare const Bucket: z.ZodPipe<z.ZodObject<{
|
|
|
131
131
|
type Bucket = z.infer<typeof Bucket>;
|
|
132
132
|
declare const Config: z.ZodObject<{
|
|
133
133
|
locales: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
134
|
+
fallbackLocales: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString>]>>>;
|
|
134
135
|
buckets: z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
135
136
|
include: z.ZodArray<z.ZodString>;
|
|
136
137
|
exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require("./index.cjs");
|
|
2
|
+
const require_hash = require("./hash-8It7TJpB.cjs");
|
|
3
|
+
let node_fs_promises = require("node:fs/promises");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
//#region src/features/catalogue/merge.ts
|
|
6
|
+
function mergeUnique(...items) {
|
|
7
|
+
return Array.from(new Set(items.flat()));
|
|
8
|
+
}
|
|
9
|
+
function getMessageKey(message) {
|
|
10
|
+
return message.id ?? require_hash.generateHash(message.message, message.context);
|
|
11
|
+
}
|
|
12
|
+
function mergeExtractedMessages(messages) {
|
|
13
|
+
const mergedMessages = messages.reduce((map, message) => {
|
|
14
|
+
const key = getMessageKey(message);
|
|
15
|
+
const existing = map.get(key) ?? message;
|
|
16
|
+
map.set(key, {
|
|
17
|
+
...existing,
|
|
18
|
+
comments: mergeUnique(...existing.comments, ...message.comments),
|
|
19
|
+
references: mergeUnique(...existing.references, ...message.references)
|
|
20
|
+
});
|
|
21
|
+
return map;
|
|
22
|
+
}, /* @__PURE__ */ new Map());
|
|
23
|
+
return Array.from(mergedMessages.values());
|
|
24
|
+
}
|
|
25
|
+
function reconcileLocaleMessages(existingMessages, nextMessages) {
|
|
26
|
+
const existingMessagesByKey = existingMessages.reduce((map, message) => {
|
|
27
|
+
map.set(getMessageKey(message), message);
|
|
28
|
+
return map;
|
|
29
|
+
}, /* @__PURE__ */ new Map());
|
|
30
|
+
const reconciledMessages = nextMessages.reduce((map, message) => {
|
|
31
|
+
const key = getMessageKey(message);
|
|
32
|
+
const existingMessage = existingMessagesByKey.get(key);
|
|
33
|
+
map.set(key, {
|
|
34
|
+
...message,
|
|
35
|
+
translation: existingMessage?.translation
|
|
36
|
+
});
|
|
37
|
+
return map;
|
|
38
|
+
}, /* @__PURE__ */ new Map());
|
|
39
|
+
return Array.from(reconciledMessages.values());
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/features/catalogue/path.ts
|
|
43
|
+
function expandBucketOutputPath(bucket, locale, extension = bucket.formatter.extension) {
|
|
44
|
+
return (0, node_path.resolve)(bucket.output.replaceAll("{locale}", locale).replaceAll("{extension}", extension.slice(1)));
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/features/catalogue/storage.ts
|
|
48
|
+
const DECLARATION_CONTENT = `
|
|
49
|
+
declare const messages: Record<string, string>;
|
|
50
|
+
export default messages;
|
|
51
|
+
`.trim();
|
|
52
|
+
async function readCatalogueMessages(bucket, locale, path = expandBucketOutputPath(bucket, locale)) {
|
|
53
|
+
const content = await (0, node_fs_promises.readFile)(path, "utf8").catch(() => "");
|
|
54
|
+
if (!content) return [];
|
|
55
|
+
return bucket.formatter.parse(content);
|
|
56
|
+
}
|
|
57
|
+
async function writeCatalogueMessages(bucket, locale, messages, path = expandBucketOutputPath(bucket, locale)) {
|
|
58
|
+
const existingContent = await (0, node_fs_promises.readFile)(path, "utf8").catch(() => void 0);
|
|
59
|
+
const catalogueContent = bucket.formatter.stringify(messages, {
|
|
60
|
+
locale,
|
|
61
|
+
existingContent
|
|
62
|
+
});
|
|
63
|
+
const declarationPath = `${path}.d.ts`;
|
|
64
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
|
|
65
|
+
await Promise.all([(0, node_fs_promises.writeFile)(path, catalogueContent), (0, node_fs_promises.writeFile)(declarationPath, DECLARATION_CONTENT)]);
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
Object.defineProperty(exports, "expandBucketOutputPath", {
|
|
69
|
+
enumerable: true,
|
|
70
|
+
get: function() {
|
|
71
|
+
return expandBucketOutputPath;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
Object.defineProperty(exports, "mergeExtractedMessages", {
|
|
75
|
+
enumerable: true,
|
|
76
|
+
get: function() {
|
|
77
|
+
return mergeExtractedMessages;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
Object.defineProperty(exports, "readCatalogueMessages", {
|
|
81
|
+
enumerable: true,
|
|
82
|
+
get: function() {
|
|
83
|
+
return readCatalogueMessages;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
Object.defineProperty(exports, "reconcileLocaleMessages", {
|
|
87
|
+
enumerable: true,
|
|
88
|
+
get: function() {
|
|
89
|
+
return reconcileLocaleMessages;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
Object.defineProperty(exports, "writeCatalogueMessages", {
|
|
93
|
+
enumerable: true,
|
|
94
|
+
get: function() {
|
|
95
|
+
return writeCatalogueMessages;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { t as generateHash } from "./hash-DyC8GzMa.mjs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
//#region src/features/catalogue/merge.ts
|
|
5
|
+
function mergeUnique(...items) {
|
|
6
|
+
return Array.from(new Set(items.flat()));
|
|
7
|
+
}
|
|
8
|
+
function getMessageKey(message) {
|
|
9
|
+
return message.id ?? generateHash(message.message, message.context);
|
|
10
|
+
}
|
|
11
|
+
function mergeExtractedMessages(messages) {
|
|
12
|
+
const mergedMessages = messages.reduce((map, message) => {
|
|
13
|
+
const key = getMessageKey(message);
|
|
14
|
+
const existing = map.get(key) ?? message;
|
|
15
|
+
map.set(key, {
|
|
16
|
+
...existing,
|
|
17
|
+
comments: mergeUnique(...existing.comments, ...message.comments),
|
|
18
|
+
references: mergeUnique(...existing.references, ...message.references)
|
|
19
|
+
});
|
|
20
|
+
return map;
|
|
21
|
+
}, /* @__PURE__ */ new Map());
|
|
22
|
+
return Array.from(mergedMessages.values());
|
|
23
|
+
}
|
|
24
|
+
function reconcileLocaleMessages(existingMessages, nextMessages) {
|
|
25
|
+
const existingMessagesByKey = existingMessages.reduce((map, message) => {
|
|
26
|
+
map.set(getMessageKey(message), message);
|
|
27
|
+
return map;
|
|
28
|
+
}, /* @__PURE__ */ new Map());
|
|
29
|
+
const reconciledMessages = nextMessages.reduce((map, message) => {
|
|
30
|
+
const key = getMessageKey(message);
|
|
31
|
+
const existingMessage = existingMessagesByKey.get(key);
|
|
32
|
+
map.set(key, {
|
|
33
|
+
...message,
|
|
34
|
+
translation: existingMessage?.translation
|
|
35
|
+
});
|
|
36
|
+
return map;
|
|
37
|
+
}, /* @__PURE__ */ new Map());
|
|
38
|
+
return Array.from(reconciledMessages.values());
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/features/catalogue/path.ts
|
|
42
|
+
function expandBucketOutputPath(bucket, locale, extension = bucket.formatter.extension) {
|
|
43
|
+
return resolve(bucket.output.replaceAll("{locale}", locale).replaceAll("{extension}", extension.slice(1)));
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/features/catalogue/storage.ts
|
|
47
|
+
const DECLARATION_CONTENT = `
|
|
48
|
+
declare const messages: Record<string, string>;
|
|
49
|
+
export default messages;
|
|
50
|
+
`.trim();
|
|
51
|
+
async function readCatalogueMessages(bucket, locale, path = expandBucketOutputPath(bucket, locale)) {
|
|
52
|
+
const content = await readFile(path, "utf8").catch(() => "");
|
|
53
|
+
if (!content) return [];
|
|
54
|
+
return bucket.formatter.parse(content);
|
|
55
|
+
}
|
|
56
|
+
async function writeCatalogueMessages(bucket, locale, messages, path = expandBucketOutputPath(bucket, locale)) {
|
|
57
|
+
const existingContent = await readFile(path, "utf8").catch(() => void 0);
|
|
58
|
+
const catalogueContent = bucket.formatter.stringify(messages, {
|
|
59
|
+
locale,
|
|
60
|
+
existingContent
|
|
61
|
+
});
|
|
62
|
+
const declarationPath = `${path}.d.ts`;
|
|
63
|
+
await mkdir(dirname(path), { recursive: true });
|
|
64
|
+
await Promise.all([writeFile(path, catalogueContent), writeFile(declarationPath, DECLARATION_CONTENT)]);
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { reconcileLocaleMessages as a, mergeExtractedMessages as i, writeCatalogueMessages as n, expandBucketOutputPath as r, readCatalogueMessages as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saykit/config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI and configuration tooling for saykit",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -37,6 +37,16 @@
|
|
|
37
37
|
"default": "./dist/index.mjs"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
|
+
"./features/catalogue": {
|
|
41
|
+
"require": {
|
|
42
|
+
"types": "./dist/features/catalogue/index.d.cts",
|
|
43
|
+
"default": "./dist/features/catalogue/index.cjs"
|
|
44
|
+
},
|
|
45
|
+
"import": {
|
|
46
|
+
"types": "./dist/features/catalogue/index.d.mts",
|
|
47
|
+
"default": "./dist/features/catalogue/index.mjs"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
40
50
|
"./features/loader": {
|
|
41
51
|
"require": {
|
|
42
52
|
"types": "./dist/features/loader/index.d.cts",
|
|
@@ -66,7 +76,7 @@
|
|
|
66
76
|
"@commander-js/extra-typings": "^14.0.0",
|
|
67
77
|
"commander": "^14.0.3",
|
|
68
78
|
"js-sha256": "^0.11.1",
|
|
69
|
-
"picomatch": "^4.0.
|
|
79
|
+
"picomatch": "^4.0.5",
|
|
70
80
|
"zod": "^4.4.3"
|
|
71
81
|
},
|
|
72
82
|
"devDependencies": {
|
|
@@ -82,8 +92,6 @@
|
|
|
82
92
|
},
|
|
83
93
|
"scripts": {
|
|
84
94
|
"check": "tsc --noEmit",
|
|
85
|
-
"test": "vitest",
|
|
86
|
-
"coverage": "vitest run --coverage",
|
|
87
95
|
"build": "tsdown"
|
|
88
96
|
}
|
|
89
97
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|