@lang-tag/cli 0.13.1 → 0.15.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/algorithms/collector/dictionary-collector.d.ts +17 -0
- package/algorithms/collector/index.d.ts +10 -0
- package/algorithms/collector/namespace-collector.d.ts +12 -0
- package/algorithms/collector/type.d.ts +12 -0
- package/algorithms/config-generation/index.d.ts +1 -0
- package/algorithms/config-generation/path-based-config-generator.d.ts +25 -0
- package/algorithms/config-generation/prepend-namespace-to-path.d.ts +28 -0
- package/algorithms/index.cjs +182 -5
- package/algorithms/index.d.ts +6 -3
- package/algorithms/index.js +194 -16
- package/config.d.ts +58 -47
- package/index.cjs +86 -122
- package/index.js +82 -118
- package/namespace-collector-DCruv_PK.js +95 -0
- package/namespace-collector-DRnZvkDR.cjs +94 -0
- package/package.json +1 -1
- package/template/base-app.mustache +11 -16
- package/template/base-library.mustache +24 -33
package/algorithms/index.js
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { T as TranslationsCollector, e as $LT_RemoveFile, c as $LT_EnsureDirectoryExists } from "../namespace-collector-DCruv_PK.js";
|
|
2
|
+
import { N } from "../namespace-collector-DCruv_PK.js";
|
|
3
|
+
import path, { resolve, sep } from "pathe";
|
|
4
|
+
import process__default from "node:process";
|
|
2
5
|
import * as caseLib from "case";
|
|
3
|
-
|
|
6
|
+
class DictionaryCollector extends TranslationsCollector {
|
|
7
|
+
constructor(options = {
|
|
8
|
+
appendNamespaceToPath: false
|
|
9
|
+
}) {
|
|
10
|
+
super();
|
|
11
|
+
this.options = options;
|
|
12
|
+
}
|
|
13
|
+
clean;
|
|
14
|
+
aggregateCollection(namespace) {
|
|
15
|
+
return this.config.baseLanguageCode;
|
|
16
|
+
}
|
|
17
|
+
transformTag(tag) {
|
|
18
|
+
const originalPath = tag.parameterConfig.path;
|
|
19
|
+
let path2 = originalPath;
|
|
20
|
+
if (this.options.appendNamespaceToPath) {
|
|
21
|
+
path2 = tag.parameterConfig.namespace;
|
|
22
|
+
if (originalPath) {
|
|
23
|
+
path2 += ".";
|
|
24
|
+
path2 += originalPath;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...tag,
|
|
28
|
+
parameterConfig: {
|
|
29
|
+
...tag.parameterConfig,
|
|
30
|
+
namespace: void 0,
|
|
31
|
+
path: path2
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return tag;
|
|
36
|
+
}
|
|
37
|
+
async preWrite(clean) {
|
|
38
|
+
this.clean = clean;
|
|
39
|
+
const baseDictionaryFile = path.join(this.config.localesDirectory, `${this.config.baseLanguageCode}.json`);
|
|
40
|
+
if (clean) {
|
|
41
|
+
this.logger.info("Removing {file}", { file: baseDictionaryFile });
|
|
42
|
+
await $LT_RemoveFile(baseDictionaryFile);
|
|
43
|
+
}
|
|
44
|
+
await $LT_EnsureDirectoryExists(this.config.localesDirectory);
|
|
45
|
+
}
|
|
46
|
+
async resolveCollectionFilePath(baseLanguageCode) {
|
|
47
|
+
return resolve(
|
|
48
|
+
process__default.cwd(),
|
|
49
|
+
this.config.localesDirectory,
|
|
50
|
+
baseLanguageCode + ".json"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
async onMissingCollection(baseLanguageCode) {
|
|
54
|
+
if (!this.clean) {
|
|
55
|
+
this.logger.warn(`Original dictionary file "{namespace}.json" not found. A new one will be created.`, { namespace: baseLanguageCode });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async postWrite(changedCollections) {
|
|
59
|
+
if (!changedCollections?.length) {
|
|
60
|
+
this.logger.info("No changes were made based on the current configuration and files");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (changedCollections.length > 1) {
|
|
64
|
+
throw new Error("Should not write more than 1 collection! Only 1 base language dictionary expected!");
|
|
65
|
+
}
|
|
66
|
+
const dict = resolve(
|
|
67
|
+
this.config.localesDirectory,
|
|
68
|
+
this.config.baseLanguageCode + ".json"
|
|
69
|
+
);
|
|
70
|
+
this.logger.success("Updated dictionary {dict}", { dict });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const TRIGGER_NAME$2 = "path-based-config-generator";
|
|
4
74
|
function pathBasedConfigGenerator(options = {}) {
|
|
5
75
|
const {
|
|
6
76
|
includeFileName = false,
|
|
@@ -56,11 +126,13 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
56
126
|
}
|
|
57
127
|
pathSegments = pathSegments.filter((seg) => !ignoreDirectories.includes(seg));
|
|
58
128
|
let namespace;
|
|
59
|
-
let
|
|
129
|
+
let path2;
|
|
60
130
|
if (pathSegments.length >= 1) {
|
|
61
131
|
namespace = pathSegments[0];
|
|
62
132
|
if (pathSegments.length > 1) {
|
|
63
|
-
|
|
133
|
+
path2 = pathSegments.slice(1).join(".");
|
|
134
|
+
} else {
|
|
135
|
+
path2 = "";
|
|
64
136
|
}
|
|
65
137
|
} else {
|
|
66
138
|
namespace = actualFallbackNamespace;
|
|
@@ -73,22 +145,22 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
73
145
|
namespace = applyCaseTransform(namespace, namespaceCase);
|
|
74
146
|
}
|
|
75
147
|
}
|
|
76
|
-
if (
|
|
77
|
-
const pathParts =
|
|
148
|
+
if (path2 && pathCase) {
|
|
149
|
+
const pathParts = path2.split(".");
|
|
78
150
|
const transformedParts = pathParts.map((part) => applyCaseTransform(part, pathCase));
|
|
79
|
-
|
|
151
|
+
path2 = transformedParts.join(".");
|
|
80
152
|
}
|
|
81
153
|
const newConfig = event.config ? { ...event.config } : {};
|
|
82
154
|
if (clearOnDefaultNamespace && namespace === actualFallbackNamespace) {
|
|
83
|
-
if (
|
|
84
|
-
newConfig.path =
|
|
155
|
+
if (path2) {
|
|
156
|
+
newConfig.path = path2;
|
|
85
157
|
delete newConfig.namespace;
|
|
86
158
|
} else {
|
|
87
159
|
const hasOtherProperties = event.config && Object.keys(event.config).some(
|
|
88
160
|
(key) => key !== "namespace" && key !== "path"
|
|
89
161
|
);
|
|
90
162
|
if (!hasOtherProperties) {
|
|
91
|
-
event.save(null, TRIGGER_NAME$
|
|
163
|
+
event.save(null, TRIGGER_NAME$2);
|
|
92
164
|
return;
|
|
93
165
|
} else {
|
|
94
166
|
delete newConfig.namespace;
|
|
@@ -99,14 +171,14 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
99
171
|
if (namespace) {
|
|
100
172
|
newConfig.namespace = namespace;
|
|
101
173
|
}
|
|
102
|
-
if (
|
|
103
|
-
newConfig.path =
|
|
174
|
+
if (path2) {
|
|
175
|
+
newConfig.path = path2;
|
|
104
176
|
} else {
|
|
105
177
|
delete newConfig.path;
|
|
106
178
|
}
|
|
107
179
|
}
|
|
108
180
|
if (Object.keys(newConfig).length > 0) {
|
|
109
|
-
event.save(newConfig, TRIGGER_NAME$
|
|
181
|
+
event.save(newConfig, TRIGGER_NAME$2);
|
|
110
182
|
}
|
|
111
183
|
};
|
|
112
184
|
}
|
|
@@ -144,11 +216,69 @@ function applyStructuredIgnore(segments, structure) {
|
|
|
144
216
|
}
|
|
145
217
|
return result;
|
|
146
218
|
}
|
|
219
|
+
function addPathPrefixAndSegments(result, pathPrefix, remainingSegments) {
|
|
220
|
+
if (pathPrefix && remainingSegments.length > 0) {
|
|
221
|
+
const cleanPrefix = pathPrefix.endsWith(".") ? pathPrefix.slice(0, -1) : pathPrefix;
|
|
222
|
+
result.push(cleanPrefix, ...remainingSegments);
|
|
223
|
+
} else if (pathPrefix && remainingSegments.length === 0) {
|
|
224
|
+
const cleanPrefix = pathPrefix.endsWith(".") ? pathPrefix.slice(0, -1) : pathPrefix;
|
|
225
|
+
result.push(cleanPrefix);
|
|
226
|
+
} else if (remainingSegments.length > 0) {
|
|
227
|
+
result.push(...remainingSegments);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function processNamespaceRedirect(redirectRule, remainingSegments, options) {
|
|
231
|
+
const result = [];
|
|
232
|
+
if (redirectRule === null || redirectRule === void 0) {
|
|
233
|
+
if (options?.currentSegment !== void 0) {
|
|
234
|
+
if (!options.ignoreSelf) {
|
|
235
|
+
result.push(options.renameTo || options.currentSegment);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
result.push(...remainingSegments);
|
|
239
|
+
} else if (typeof redirectRule === "string") {
|
|
240
|
+
if (redirectRule === "") {
|
|
241
|
+
if (options?.currentSegment !== void 0) {
|
|
242
|
+
if (!options.ignoreSelf) {
|
|
243
|
+
result.push(options.renameTo || options.currentSegment);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
result.push(...remainingSegments);
|
|
247
|
+
} else {
|
|
248
|
+
result.push(redirectRule);
|
|
249
|
+
result.push(...remainingSegments);
|
|
250
|
+
}
|
|
251
|
+
} else if (typeof redirectRule === "object" && redirectRule !== null) {
|
|
252
|
+
const namespace = redirectRule.namespace;
|
|
253
|
+
const pathPrefix = redirectRule.pathPrefix || "";
|
|
254
|
+
if (namespace === void 0 || namespace === null || namespace === "") {
|
|
255
|
+
if (options?.currentSegment !== void 0) {
|
|
256
|
+
if (!options.ignoreSelf) {
|
|
257
|
+
result.push(options.renameTo || options.currentSegment);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
addPathPrefixAndSegments(result, pathPrefix, remainingSegments);
|
|
261
|
+
} else {
|
|
262
|
+
result.push(namespace);
|
|
263
|
+
addPathPrefixAndSegments(result, pathPrefix, remainingSegments);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
147
268
|
function applyPathRules(segments, structure) {
|
|
148
269
|
const result = [];
|
|
149
270
|
let currentStructure = structure;
|
|
271
|
+
let deepestRedirect = null;
|
|
150
272
|
for (let i = 0; i < segments.length; i++) {
|
|
151
273
|
const segment = segments[i];
|
|
274
|
+
if (">>" in currentStructure && (!deepestRedirect || !deepestRedirect.context)) {
|
|
275
|
+
const redirectRule = currentStructure[">>"];
|
|
276
|
+
const remainingSegments = segments.slice(i);
|
|
277
|
+
deepestRedirect = {
|
|
278
|
+
rule: redirectRule,
|
|
279
|
+
remainingSegments
|
|
280
|
+
};
|
|
281
|
+
}
|
|
152
282
|
if (segment in currentStructure) {
|
|
153
283
|
const rule = currentStructure[segment];
|
|
154
284
|
if (rule === true) {
|
|
@@ -171,6 +301,22 @@ function applyPathRules(segments, structure) {
|
|
|
171
301
|
} else if (typeof rule === "object" && rule !== null) {
|
|
172
302
|
const ignoreSelf = rule["_"] === false;
|
|
173
303
|
const renameTo = rule[">"];
|
|
304
|
+
const redirectRule = rule[">>"];
|
|
305
|
+
if (">>" in rule) {
|
|
306
|
+
const remainingSegments = segments.slice(i + 1);
|
|
307
|
+
const ruleWithoutRedirect = { ...rule };
|
|
308
|
+
delete ruleWithoutRedirect[">>"];
|
|
309
|
+
const processedRemaining = applyPathRules(remainingSegments, ruleWithoutRedirect);
|
|
310
|
+
deepestRedirect = {
|
|
311
|
+
rule: redirectRule,
|
|
312
|
+
remainingSegments: processedRemaining,
|
|
313
|
+
context: {
|
|
314
|
+
currentSegment: segment,
|
|
315
|
+
renameTo,
|
|
316
|
+
ignoreSelf
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
174
320
|
if (!ignoreSelf) {
|
|
175
321
|
if (typeof renameTo === "string") {
|
|
176
322
|
result.push(renameTo);
|
|
@@ -185,6 +331,13 @@ function applyPathRules(segments, structure) {
|
|
|
185
331
|
result.push(segment);
|
|
186
332
|
currentStructure = structure;
|
|
187
333
|
}
|
|
334
|
+
if (deepestRedirect) {
|
|
335
|
+
return processNamespaceRedirect(
|
|
336
|
+
deepestRedirect.rule,
|
|
337
|
+
deepestRedirect.remainingSegments,
|
|
338
|
+
deepestRedirect.context
|
|
339
|
+
);
|
|
340
|
+
}
|
|
188
341
|
return result;
|
|
189
342
|
}
|
|
190
343
|
function applyCaseTransform(str, caseType) {
|
|
@@ -211,7 +364,7 @@ function extractRootDirectoriesFromIncludes(includes) {
|
|
|
211
364
|
}
|
|
212
365
|
return Array.from(directories);
|
|
213
366
|
}
|
|
214
|
-
const TRIGGER_NAME = "config-keeper";
|
|
367
|
+
const TRIGGER_NAME$1 = "config-keeper";
|
|
215
368
|
function configKeeper(options = {}) {
|
|
216
369
|
const propertyName = options.propertyName ?? "keep";
|
|
217
370
|
const keepPropertyAtEnd = options.keepPropertyAtEnd ?? true;
|
|
@@ -268,10 +421,35 @@ function configKeeper(options = {}) {
|
|
|
268
421
|
delete finalConfig[propertyName];
|
|
269
422
|
}
|
|
270
423
|
finalConfig[propertyName] = keepMode;
|
|
271
|
-
event.save(finalConfig, TRIGGER_NAME);
|
|
424
|
+
event.save(finalConfig, TRIGGER_NAME$1);
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
const TRIGGER_NAME = "prepend-namespace-to-path";
|
|
428
|
+
function prependNamespaceToPath(options = {}) {
|
|
429
|
+
return async (event) => {
|
|
430
|
+
const currentConfig = event.savedConfig;
|
|
431
|
+
const { namespace, path: path2 } = currentConfig || {};
|
|
432
|
+
const actualNamespace = namespace || event.langTagConfig.collect?.defaultNamespace;
|
|
433
|
+
if (!actualNamespace) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
let newPath;
|
|
437
|
+
if (path2) {
|
|
438
|
+
newPath = `${actualNamespace}.${path2}`;
|
|
439
|
+
} else {
|
|
440
|
+
newPath = actualNamespace;
|
|
441
|
+
}
|
|
442
|
+
event.save({
|
|
443
|
+
...currentConfig || {},
|
|
444
|
+
path: newPath,
|
|
445
|
+
namespace: void 0
|
|
446
|
+
}, TRIGGER_NAME);
|
|
272
447
|
};
|
|
273
448
|
}
|
|
274
449
|
export {
|
|
450
|
+
DictionaryCollector,
|
|
451
|
+
N as NamespaceCollector,
|
|
275
452
|
configKeeper,
|
|
276
|
-
pathBasedConfigGenerator
|
|
453
|
+
pathBasedConfigGenerator,
|
|
454
|
+
prependNamespaceToPath
|
|
277
455
|
};
|
package/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LangTagTranslationsConfig } from 'lang-tag';
|
|
2
2
|
import { LangTagCLILogger } from './logger.ts';
|
|
3
|
+
import { TranslationsCollector } from './algorithms/collector/type.ts';
|
|
3
4
|
export interface LangTagCLIConfig {
|
|
4
5
|
/**
|
|
5
6
|
* Tag name used to mark translations in code.
|
|
@@ -17,11 +18,37 @@ export interface LangTagCLIConfig {
|
|
|
17
18
|
*/
|
|
18
19
|
excludes: string[];
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* Root directory for translation files.
|
|
22
|
+
* The actual file structure depends on the collector implementation used.
|
|
23
|
+
* @default 'locales'
|
|
24
|
+
* @example With baseLanguageCode='en' and localesDirectory='locales':
|
|
25
|
+
* - NamespaceCollector (default): locales/en/common.json, locales/en/errors.json
|
|
26
|
+
* - DictionaryCollector: locales/en.json (all translations in one file)
|
|
22
27
|
*/
|
|
23
|
-
|
|
28
|
+
localesDirectory: string;
|
|
29
|
+
/**
|
|
30
|
+
* The language in which translation values/messages are written in the codebase.
|
|
31
|
+
* This determines the source language for your translations.
|
|
32
|
+
* @default 'en'
|
|
33
|
+
* @example 'en' - Translation values are in English: lang({ helloWorld: 'Hello World' })
|
|
34
|
+
* @example 'pl' - Translation values are in Polish: lang({ helloWorld: 'Witaj Świecie' })
|
|
35
|
+
*/
|
|
36
|
+
baseLanguageCode: string;
|
|
37
|
+
/**
|
|
38
|
+
* Indicates whether this configuration is for a translation library.
|
|
39
|
+
* If true, generates an exports file (`.lang-tag.exports.json`) instead of locale files.
|
|
40
|
+
* @default false
|
|
41
|
+
*/
|
|
42
|
+
isLibrary: boolean;
|
|
24
43
|
collect?: {
|
|
44
|
+
/**
|
|
45
|
+
* Translation collector that defines how translation tags are organized into output files.
|
|
46
|
+
* If not specified, NamespaceCollector is used by default.
|
|
47
|
+
* @default NamespaceCollector
|
|
48
|
+
* @example DictionaryCollector - All translations in single file per language
|
|
49
|
+
* @example NamespaceCollector - Separate files per namespace within language directory
|
|
50
|
+
*/
|
|
51
|
+
collector?: TranslationsCollector;
|
|
25
52
|
/**
|
|
26
53
|
* @default 'common'
|
|
27
54
|
*/
|
|
@@ -50,50 +77,6 @@ export interface LangTagCLIConfig {
|
|
|
50
77
|
*/
|
|
51
78
|
onCollectFinish?: (event: LangTagCLICollectFinishEvent) => void;
|
|
52
79
|
};
|
|
53
|
-
import: {
|
|
54
|
-
/**
|
|
55
|
-
* Output directory for generated files containing imported library tags.
|
|
56
|
-
* @default 'src/lang-libraries'
|
|
57
|
-
*/
|
|
58
|
-
dir: string;
|
|
59
|
-
/**
|
|
60
|
-
* The import statement used in generated library files to import the project's `lang` tag function.
|
|
61
|
-
* @default 'import { lang } from "@/my-lang-tag-path"'
|
|
62
|
-
*/
|
|
63
|
-
tagImportPath: string;
|
|
64
|
-
/**
|
|
65
|
-
* A function to customize the generated file name and export name for imported library tags.
|
|
66
|
-
* Allows controlling how imported tags are organized and named within the generated files.
|
|
67
|
-
*/
|
|
68
|
-
onImport: (params: LangTagCLIOnImportParams, actions: LangTagCLIOnImportActions) => void;
|
|
69
|
-
/**
|
|
70
|
-
* A function called after all lang-tags were imported
|
|
71
|
-
*/
|
|
72
|
-
onImportFinish?: () => void;
|
|
73
|
-
};
|
|
74
|
-
/**
|
|
75
|
-
* Determines the position of the translation argument in the `lang()` function.
|
|
76
|
-
* If `1`, translations are in the first argument (`lang(translations, options)`).
|
|
77
|
-
* If `2`, translations are in the second argument (`lang(options, translations)`).
|
|
78
|
-
* @default 1
|
|
79
|
-
*/
|
|
80
|
-
translationArgPosition: 1 | 2;
|
|
81
|
-
/**
|
|
82
|
-
* Primary language used for the library's translations.
|
|
83
|
-
* Affects default language settings when used in library mode.
|
|
84
|
-
* @default 'en'
|
|
85
|
-
*/
|
|
86
|
-
language: string;
|
|
87
|
-
/**
|
|
88
|
-
* Indicates whether this configuration is for a translation library.
|
|
89
|
-
* If true, generates an exports file (`.lang-tag.exports.json`) instead of locale files.
|
|
90
|
-
* @default false
|
|
91
|
-
*/
|
|
92
|
-
isLibrary: boolean;
|
|
93
|
-
/**
|
|
94
|
-
* Whether to flatten the translation keys. (Currently unused)
|
|
95
|
-
* @default false
|
|
96
|
-
*/
|
|
97
80
|
/**
|
|
98
81
|
* A function called for each found lang tag before processing.
|
|
99
82
|
* Allows dynamic modification of the tag's configuration (namespace, path, etc.)
|
|
@@ -123,6 +106,34 @@ export interface LangTagCLIConfig {
|
|
|
123
106
|
* ```
|
|
124
107
|
*/
|
|
125
108
|
onConfigGeneration: (event: LangTagCLIConfigGenerationEvent) => Promise<void>;
|
|
109
|
+
import: {
|
|
110
|
+
/**
|
|
111
|
+
* Output directory for generated files containing imported library tags.
|
|
112
|
+
* @default 'src/lang-libraries'
|
|
113
|
+
*/
|
|
114
|
+
dir: string;
|
|
115
|
+
/**
|
|
116
|
+
* The import statement used in generated library files to import the project's `lang` tag function.
|
|
117
|
+
* @default 'import { lang } from "@/my-lang-tag-path"'
|
|
118
|
+
*/
|
|
119
|
+
tagImportPath: string;
|
|
120
|
+
/**
|
|
121
|
+
* A function to customize the generated file name and export name for imported library tags.
|
|
122
|
+
* Allows controlling how imported tags are organized and named within the generated files.
|
|
123
|
+
*/
|
|
124
|
+
onImport: (params: LangTagCLIOnImportParams, actions: LangTagCLIOnImportActions) => void;
|
|
125
|
+
/**
|
|
126
|
+
* A function called after all lang-tags were imported
|
|
127
|
+
*/
|
|
128
|
+
onImportFinish?: () => void;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Determines the position of the translation argument in the `lang()` function.
|
|
132
|
+
* If `1`, translations are in the first argument (`lang(translations, options)`).
|
|
133
|
+
* If `2`, translations are in the second argument (`lang(options, translations)`).
|
|
134
|
+
* @default 1
|
|
135
|
+
*/
|
|
136
|
+
translationArgPosition: 1 | 2;
|
|
126
137
|
debug?: boolean;
|
|
127
138
|
}
|
|
128
139
|
/**
|