@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.
@@ -0,0 +1,17 @@
1
+ import { TranslationsCollector } from './type.ts';
2
+ import { LangTagCLIProcessedTag } from '../../config.ts';
3
+ interface Options {
4
+ appendNamespaceToPath: boolean;
5
+ }
6
+ export declare class DictionaryCollector extends TranslationsCollector {
7
+ private readonly options;
8
+ private clean;
9
+ constructor(options?: Options);
10
+ aggregateCollection(namespace: string): string;
11
+ transformTag(tag: LangTagCLIProcessedTag): LangTagCLIProcessedTag;
12
+ preWrite(clean: boolean): Promise<void>;
13
+ resolveCollectionFilePath(baseLanguageCode: string): Promise<any>;
14
+ onMissingCollection(baseLanguageCode: string): Promise<void>;
15
+ postWrite(changedCollections: string[]): Promise<void>;
16
+ }
17
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Translation collectors for organizing translation tags into output files.
3
+ *
4
+ * These collectors define how translation tags are grouped and written to files
5
+ * during the collection process. Each collector implements a different strategy
6
+ * for organizing translations (e.g., single dictionary vs. namespace-based files).
7
+ */
8
+ export { DictionaryCollector } from './dictionary-collector.ts';
9
+ export { NamespaceCollector } from './namespace-collector.ts';
10
+ export { type TranslationsCollector } from './type.ts';
@@ -0,0 +1,12 @@
1
+ import { TranslationsCollector } from './type.ts';
2
+ import { LangTagCLIProcessedTag } from '../../config.ts';
3
+ export declare class NamespaceCollector extends TranslationsCollector {
4
+ private clean;
5
+ private languageDirectory;
6
+ aggregateCollection(namespace: string): string;
7
+ transformTag(tag: LangTagCLIProcessedTag): LangTagCLIProcessedTag;
8
+ preWrite(clean: boolean): Promise<void>;
9
+ resolveCollectionFilePath(collectionName: string): Promise<any>;
10
+ onMissingCollection(collectionName: string): Promise<void>;
11
+ postWrite(changedCollections: string[]): Promise<void>;
12
+ }
@@ -0,0 +1,12 @@
1
+ import { LangTagCLIConfig, LangTagCLIProcessedTag } from '../../config.ts';
2
+ import { LangTagCLILogger } from '../../logger.ts';
3
+ export declare abstract class TranslationsCollector {
4
+ config: LangTagCLIConfig;
5
+ logger: LangTagCLILogger;
6
+ abstract aggregateCollection(namespace: string): string;
7
+ abstract transformTag(tag: LangTagCLIProcessedTag): LangTagCLIProcessedTag;
8
+ abstract preWrite(clean?: boolean): Promise<void>;
9
+ abstract resolveCollectionFilePath(collectionName: string): Promise<string>;
10
+ abstract onMissingCollection(collectionName: string): Promise<void>;
11
+ abstract postWrite(changedCollections: string[]): Promise<void>;
12
+ }
@@ -6,3 +6,4 @@
6
6
  */
7
7
  export { pathBasedConfigGenerator, type PathBasedConfigGeneratorOptions } from './path-based-config-generator.ts';
8
8
  export { configKeeper, type ConfigKeeperOptions, type ConfigKeeperMode } from './config-keeper.ts';
9
+ export { prependNamespaceToPath, type PrependNamespaceToPathOptions } from './prepend-namespace-to-path.ts';
@@ -67,6 +67,12 @@ export interface PathBasedConfigGeneratorOptions {
67
67
  * Supports ignoring and renaming segments with special keys:
68
68
  * - `_`: when `false`, ignores the current segment but continues hierarchy
69
69
  * - `>`: renames the current segment to the specified value
70
+ * - `>>`: namespace redirect - jumps to a different namespace and continues processing remaining path segments
71
+ * - String: `>>: 'namespace'` - redirects to specified namespace, remaining segments become path
72
+ * - Object: `>>: { namespace: 'name', pathPrefix: 'prefix.' }` - redirects with optional path prefix
73
+ * - Empty: `>>: ''` or `>>: null/undefined` - uses current segment as namespace
74
+ * - Missing namespace: `>>: { pathPrefix: 'prefix.' }` - uses current segment as namespace with prefix
75
+ * - Nested: deepest `>>` in hierarchy takes precedence
70
76
  * - Regular keys: nested rules or boolean/string for child segments
71
77
  *
72
78
  * @example
@@ -79,6 +85,25 @@ export interface PathBasedConfigGeneratorOptions {
79
85
  * admin: {
80
86
  * '>': 'management', // rename "admin" to "management"
81
87
  * users: true // keep "users" as is (does nothing)
88
+ * },
89
+ * layout: {
90
+ * '>>': 'dashboard' // redirect: everything below layout jumps to "dashboard" namespace
91
+ * },
92
+ * components: {
93
+ * '>>': { // redirect: jump to "ui" namespace with "components." path prefix
94
+ * namespace: 'ui',
95
+ * pathPrefix: 'components'
96
+ * }
97
+ * },
98
+ * features: {
99
+ * '>>': { // redirect: use current segment as namespace with "feature." prefix
100
+ * pathPrefix: 'feature.'
101
+ * }
102
+ * },
103
+ * auth: {
104
+ * '>>': '', // redirect: use current segment as namespace
105
+ * '>>': null, // same as empty string
106
+ * '>>': undefined // same as empty string
82
107
  * }
83
108
  * }
84
109
  * }
@@ -0,0 +1,28 @@
1
+ import { LangTagCLIConfigGenerationEvent } from '../../config.ts';
2
+ /**
3
+ * Options for the prependNamespaceToPath algorithm.
4
+ */
5
+ export interface PrependNamespaceToPathOptions {
6
+ }
7
+ /**
8
+ * Algorithm that prepends the namespace to the path in translation tag configurations.
9
+ *
10
+ * This is useful when you want to flatten the namespace structure by moving the namespace
11
+ * into the path, effectively removing the namespace separation.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // Before: { namespace: 'common', path: 'hello.world' }
16
+ * // After: { path: 'common.hello.world' }
17
+ *
18
+ * // Before: { namespace: 'common' }
19
+ * // After: { path: 'common' }
20
+ *
21
+ * // Before: {}
22
+ * // After: { path: 'common' }
23
+ * ```
24
+ *
25
+ * @param options Configuration options for the algorithm
26
+ * @returns A function compatible with LangTagCLIConfigGenerationEvent
27
+ */
28
+ export declare function prependNamespaceToPath(options?: PrependNamespaceToPathOptions): (event: LangTagCLIConfigGenerationEvent) => Promise<void>;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const namespaceCollector = require("../namespace-collector-DRnZvkDR.cjs");
3
4
  const path = require("pathe");
5
+ const process = require("node:process");
4
6
  const caseLib = require("case");
5
7
  function _interopNamespaceDefault(e) {
6
8
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -19,7 +21,74 @@ function _interopNamespaceDefault(e) {
19
21
  return Object.freeze(n);
20
22
  }
21
23
  const caseLib__namespace = /* @__PURE__ */ _interopNamespaceDefault(caseLib);
22
- const TRIGGER_NAME$1 = "path-based-config-generator";
24
+ class DictionaryCollector extends namespaceCollector.TranslationsCollector {
25
+ constructor(options = {
26
+ appendNamespaceToPath: false
27
+ }) {
28
+ super();
29
+ this.options = options;
30
+ }
31
+ clean;
32
+ aggregateCollection(namespace) {
33
+ return this.config.baseLanguageCode;
34
+ }
35
+ transformTag(tag) {
36
+ const originalPath = tag.parameterConfig.path;
37
+ let path2 = originalPath;
38
+ if (this.options.appendNamespaceToPath) {
39
+ path2 = tag.parameterConfig.namespace;
40
+ if (originalPath) {
41
+ path2 += ".";
42
+ path2 += originalPath;
43
+ }
44
+ return {
45
+ ...tag,
46
+ parameterConfig: {
47
+ ...tag.parameterConfig,
48
+ namespace: void 0,
49
+ path: path2
50
+ }
51
+ };
52
+ }
53
+ return tag;
54
+ }
55
+ async preWrite(clean) {
56
+ this.clean = clean;
57
+ const baseDictionaryFile = path.join(this.config.localesDirectory, `${this.config.baseLanguageCode}.json`);
58
+ if (clean) {
59
+ this.logger.info("Removing {file}", { file: baseDictionaryFile });
60
+ await namespaceCollector.$LT_RemoveFile(baseDictionaryFile);
61
+ }
62
+ await namespaceCollector.$LT_EnsureDirectoryExists(this.config.localesDirectory);
63
+ }
64
+ async resolveCollectionFilePath(baseLanguageCode) {
65
+ return path.resolve(
66
+ process.cwd(),
67
+ this.config.localesDirectory,
68
+ baseLanguageCode + ".json"
69
+ );
70
+ }
71
+ async onMissingCollection(baseLanguageCode) {
72
+ if (!this.clean) {
73
+ this.logger.warn(`Original dictionary file "{namespace}.json" not found. A new one will be created.`, { namespace: baseLanguageCode });
74
+ }
75
+ }
76
+ async postWrite(changedCollections) {
77
+ if (!changedCollections?.length) {
78
+ this.logger.info("No changes were made based on the current configuration and files");
79
+ return;
80
+ }
81
+ if (changedCollections.length > 1) {
82
+ throw new Error("Should not write more than 1 collection! Only 1 base language dictionary expected!");
83
+ }
84
+ const dict = path.resolve(
85
+ this.config.localesDirectory,
86
+ this.config.baseLanguageCode + ".json"
87
+ );
88
+ this.logger.success("Updated dictionary {dict}", { dict });
89
+ }
90
+ }
91
+ const TRIGGER_NAME$2 = "path-based-config-generator";
23
92
  function pathBasedConfigGenerator(options = {}) {
24
93
  const {
25
94
  includeFileName = false,
@@ -80,6 +149,8 @@ function pathBasedConfigGenerator(options = {}) {
80
149
  namespace = pathSegments[0];
81
150
  if (pathSegments.length > 1) {
82
151
  path$1 = pathSegments.slice(1).join(".");
152
+ } else {
153
+ path$1 = "";
83
154
  }
84
155
  } else {
85
156
  namespace = actualFallbackNamespace;
@@ -107,7 +178,7 @@ function pathBasedConfigGenerator(options = {}) {
107
178
  (key) => key !== "namespace" && key !== "path"
108
179
  );
109
180
  if (!hasOtherProperties) {
110
- event.save(null, TRIGGER_NAME$1);
181
+ event.save(null, TRIGGER_NAME$2);
111
182
  return;
112
183
  } else {
113
184
  delete newConfig.namespace;
@@ -125,7 +196,7 @@ function pathBasedConfigGenerator(options = {}) {
125
196
  }
126
197
  }
127
198
  if (Object.keys(newConfig).length > 0) {
128
- event.save(newConfig, TRIGGER_NAME$1);
199
+ event.save(newConfig, TRIGGER_NAME$2);
129
200
  }
130
201
  };
131
202
  }
@@ -163,11 +234,69 @@ function applyStructuredIgnore(segments, structure) {
163
234
  }
164
235
  return result;
165
236
  }
237
+ function addPathPrefixAndSegments(result, pathPrefix, remainingSegments) {
238
+ if (pathPrefix && remainingSegments.length > 0) {
239
+ const cleanPrefix = pathPrefix.endsWith(".") ? pathPrefix.slice(0, -1) : pathPrefix;
240
+ result.push(cleanPrefix, ...remainingSegments);
241
+ } else if (pathPrefix && remainingSegments.length === 0) {
242
+ const cleanPrefix = pathPrefix.endsWith(".") ? pathPrefix.slice(0, -1) : pathPrefix;
243
+ result.push(cleanPrefix);
244
+ } else if (remainingSegments.length > 0) {
245
+ result.push(...remainingSegments);
246
+ }
247
+ }
248
+ function processNamespaceRedirect(redirectRule, remainingSegments, options) {
249
+ const result = [];
250
+ if (redirectRule === null || redirectRule === void 0) {
251
+ if (options?.currentSegment !== void 0) {
252
+ if (!options.ignoreSelf) {
253
+ result.push(options.renameTo || options.currentSegment);
254
+ }
255
+ }
256
+ result.push(...remainingSegments);
257
+ } else if (typeof redirectRule === "string") {
258
+ if (redirectRule === "") {
259
+ if (options?.currentSegment !== void 0) {
260
+ if (!options.ignoreSelf) {
261
+ result.push(options.renameTo || options.currentSegment);
262
+ }
263
+ }
264
+ result.push(...remainingSegments);
265
+ } else {
266
+ result.push(redirectRule);
267
+ result.push(...remainingSegments);
268
+ }
269
+ } else if (typeof redirectRule === "object" && redirectRule !== null) {
270
+ const namespace = redirectRule.namespace;
271
+ const pathPrefix = redirectRule.pathPrefix || "";
272
+ if (namespace === void 0 || namespace === null || namespace === "") {
273
+ if (options?.currentSegment !== void 0) {
274
+ if (!options.ignoreSelf) {
275
+ result.push(options.renameTo || options.currentSegment);
276
+ }
277
+ }
278
+ addPathPrefixAndSegments(result, pathPrefix, remainingSegments);
279
+ } else {
280
+ result.push(namespace);
281
+ addPathPrefixAndSegments(result, pathPrefix, remainingSegments);
282
+ }
283
+ }
284
+ return result;
285
+ }
166
286
  function applyPathRules(segments, structure) {
167
287
  const result = [];
168
288
  let currentStructure = structure;
289
+ let deepestRedirect = null;
169
290
  for (let i = 0; i < segments.length; i++) {
170
291
  const segment = segments[i];
292
+ if (">>" in currentStructure && (!deepestRedirect || !deepestRedirect.context)) {
293
+ const redirectRule = currentStructure[">>"];
294
+ const remainingSegments = segments.slice(i);
295
+ deepestRedirect = {
296
+ rule: redirectRule,
297
+ remainingSegments
298
+ };
299
+ }
171
300
  if (segment in currentStructure) {
172
301
  const rule = currentStructure[segment];
173
302
  if (rule === true) {
@@ -190,6 +319,22 @@ function applyPathRules(segments, structure) {
190
319
  } else if (typeof rule === "object" && rule !== null) {
191
320
  const ignoreSelf = rule["_"] === false;
192
321
  const renameTo = rule[">"];
322
+ const redirectRule = rule[">>"];
323
+ if (">>" in rule) {
324
+ const remainingSegments = segments.slice(i + 1);
325
+ const ruleWithoutRedirect = { ...rule };
326
+ delete ruleWithoutRedirect[">>"];
327
+ const processedRemaining = applyPathRules(remainingSegments, ruleWithoutRedirect);
328
+ deepestRedirect = {
329
+ rule: redirectRule,
330
+ remainingSegments: processedRemaining,
331
+ context: {
332
+ currentSegment: segment,
333
+ renameTo,
334
+ ignoreSelf
335
+ }
336
+ };
337
+ }
193
338
  if (!ignoreSelf) {
194
339
  if (typeof renameTo === "string") {
195
340
  result.push(renameTo);
@@ -204,6 +349,13 @@ function applyPathRules(segments, structure) {
204
349
  result.push(segment);
205
350
  currentStructure = structure;
206
351
  }
352
+ if (deepestRedirect) {
353
+ return processNamespaceRedirect(
354
+ deepestRedirect.rule,
355
+ deepestRedirect.remainingSegments,
356
+ deepestRedirect.context
357
+ );
358
+ }
207
359
  return result;
208
360
  }
209
361
  function applyCaseTransform(str, caseType) {
@@ -230,7 +382,7 @@ function extractRootDirectoriesFromIncludes(includes) {
230
382
  }
231
383
  return Array.from(directories);
232
384
  }
233
- const TRIGGER_NAME = "config-keeper";
385
+ const TRIGGER_NAME$1 = "config-keeper";
234
386
  function configKeeper(options = {}) {
235
387
  const propertyName = options.propertyName ?? "keep";
236
388
  const keepPropertyAtEnd = options.keepPropertyAtEnd ?? true;
@@ -287,8 +439,33 @@ function configKeeper(options = {}) {
287
439
  delete finalConfig[propertyName];
288
440
  }
289
441
  finalConfig[propertyName] = keepMode;
290
- event.save(finalConfig, TRIGGER_NAME);
442
+ event.save(finalConfig, TRIGGER_NAME$1);
443
+ };
444
+ }
445
+ const TRIGGER_NAME = "prepend-namespace-to-path";
446
+ function prependNamespaceToPath(options = {}) {
447
+ return async (event) => {
448
+ const currentConfig = event.savedConfig;
449
+ const { namespace, path: path2 } = currentConfig || {};
450
+ const actualNamespace = namespace || event.langTagConfig.collect?.defaultNamespace;
451
+ if (!actualNamespace) {
452
+ return;
453
+ }
454
+ let newPath;
455
+ if (path2) {
456
+ newPath = `${actualNamespace}.${path2}`;
457
+ } else {
458
+ newPath = actualNamespace;
459
+ }
460
+ event.save({
461
+ ...currentConfig || {},
462
+ path: newPath,
463
+ namespace: void 0
464
+ }, TRIGGER_NAME);
291
465
  };
292
466
  }
467
+ exports.NamespaceCollector = namespaceCollector.NamespaceCollector;
468
+ exports.DictionaryCollector = DictionaryCollector;
293
469
  exports.configKeeper = configKeeper;
294
470
  exports.pathBasedConfigGenerator = pathBasedConfigGenerator;
471
+ exports.prependNamespaceToPath = prependNamespaceToPath;
@@ -1,7 +1,10 @@
1
1
  /**
2
- * Predefined algorithms for lang-tag-cli configuration.
2
+ * Algorithm modules for lang-tag-cli.
3
3
  *
4
- * These algorithms can be used in your lang-tag-cli config file
5
- * to customize how tags are processed during collection and regeneration.
4
+ * This module provides access to all available algorithms organized by category:
5
+ * - Collectors: Define how translation tags are organized into output files
6
+ * - Config Generation: Customize tag configuration generation
7
+ * - Import: Handle importing translation libraries (future)
6
8
  */
9
+ export * from './collector/index.ts';
7
10
  export * from './config-generation/index.ts';