@lang-tag/cli 0.10.1 → 0.11.1
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 +2 -2
- package/algorithms/config-generation/index.d.ts +7 -0
- package/algorithms/config-generation/path-based-config-generator.d.ts +110 -0
- package/algorithms/import/index.d.ts +7 -0
- package/algorithms/index.cjs +159 -0
- package/algorithms/index.d.ts +7 -0
- package/algorithms/index.js +142 -0
- package/config.d.ts +1 -0
- package/index.cjs +18 -12
- package/index.js +18 -12
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ The core is optimized for performance, with a bundle size of just **~1KB** ([che
|
|
|
14
14
|
|
|
15
15
|
### Effortless translation structure
|
|
16
16
|
|
|
17
|
-
Instead of manually managing centralized translation files, `lang-tag` lets you colocate keys within components and automatically organizes them into namespaces based on your project structure. For example, all components in `components/orders` or pages in `pages/order` share the `orders` namespace. You define a simple
|
|
17
|
+
Instead of manually managing centralized translation files, `lang-tag` lets you colocate keys within components and automatically organizes them into namespaces based on your project structure. For example, all components in `components/orders` or pages in `pages/order` share the `orders` namespace. You define a simple directory-to-namespace mapping once, and `lang-tag` handles merging and file organization—while you retain full control over how namespaces are merged.
|
|
18
18
|
|
|
19
19
|
> Set your rules, then let `lang-tag` do the rest.
|
|
20
20
|
|
|
@@ -23,7 +23,7 @@ Instead of manually managing centralized translation files, `lang-tag` lets you
|
|
|
23
23
|
Full functionality is available through an advanced CLI that keeps your application bundle size untouched:
|
|
24
24
|
|
|
25
25
|
- **Automatic translation collection** – `lang-tag collect` scans your project for translation tags and aggregates them into organized JSON files (e.g., `public/locales/en/common.json`), based on your configuration
|
|
26
|
-
- **Dynamic configuration updates** – `lang-tag regenerate-tags` automatically refreshes translation settings in your code, using rules defined in your configuration (e.g., mapping namespaces based on
|
|
26
|
+
- **Dynamic configuration updates** – `lang-tag regenerate-tags` automatically refreshes translation settings in your code, using rules defined in your configuration (e.g., mapping namespaces based on directory structure)
|
|
27
27
|
- **Third-party translation import** – `lang-tag import` detects and integrates translations from external libraries, adapting them to your project’s translation system
|
|
28
28
|
- **Watch mode** – `lang-tag watch` monitors your source files for changes and automatically re-collects/re-generates translations when needed
|
|
29
29
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predefined algorithms for onConfigGeneration hook.
|
|
3
|
+
*
|
|
4
|
+
* These algorithms customize how translation tag configurations are generated
|
|
5
|
+
* during collection and regeneration.
|
|
6
|
+
*/
|
|
7
|
+
export { pathBasedConfigGenerator, type PathBasedConfigGeneratorOptions } from './path-based-config-generator.ts';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { LangTagCLIConfigGenerationEvent } from '../../config.ts';
|
|
2
|
+
export interface PathBasedConfigGeneratorOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Whether to include the filename (without extension) as part of the path segments.
|
|
5
|
+
* @default false
|
|
6
|
+
*/
|
|
7
|
+
includeFileName?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Whether to completely remove directories wrapped in brackets () or [].
|
|
10
|
+
* If false, only the brackets are removed from directory names.
|
|
11
|
+
* @default true
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* - true: 'app/(admin)/users' -> 'app/users'
|
|
15
|
+
* - false: 'app/(admin)/users' -> 'app/admin/users'
|
|
16
|
+
*/
|
|
17
|
+
removeBracketedDirectories?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* List of directory names to completely ignore globally.
|
|
20
|
+
* These will be removed from all paths regardless of their position.
|
|
21
|
+
* @default []
|
|
22
|
+
*
|
|
23
|
+
* @example ['src', 'app', 'components']
|
|
24
|
+
*/
|
|
25
|
+
ignoreDirectories?: string[];
|
|
26
|
+
/**
|
|
27
|
+
* When true, automatically extracts root directory names from the config.includes patterns
|
|
28
|
+
* and adds them to the ignoreDirectories list.
|
|
29
|
+
*
|
|
30
|
+
* @default false
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // With includes: ['src/**\/*.{js,ts,jsx,tsx}']
|
|
34
|
+
* // Automatically ignores: ['src']
|
|
35
|
+
*
|
|
36
|
+
* // With includes: ['(src|app)/**\/*.{js,ts,jsx,tsx}', 'components/**\/*.{jsx,tsx}']
|
|
37
|
+
* // Automatically ignores: ['src', 'app', 'components']
|
|
38
|
+
*/
|
|
39
|
+
ignoreIncludesRootDirectories?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Hierarchical structure for ignoring specific directory patterns.
|
|
42
|
+
* Keys represent path segments to match, values indicate what to ignore at that level.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* {
|
|
46
|
+
* 'src': {
|
|
47
|
+
* 'app': true, // ignore 'app' when under 'src'
|
|
48
|
+
* 'features': ['auth', 'admin'] // ignore 'auth' and 'admin' under 'src/features'
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
*/
|
|
52
|
+
ignoreStructured?: Record<string, any>;
|
|
53
|
+
/**
|
|
54
|
+
* Convert the final namespace to lowercase.
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
lowercaseNamespace?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Case transformation to apply to the namespace.
|
|
60
|
+
* Available options: 'camel', 'capital', 'constant', 'dot', 'header', 'kebab',
|
|
61
|
+
* 'lower', 'no', 'param', 'pascal', 'path', 'sentence', 'snake', 'swap', 'title', 'upper'
|
|
62
|
+
* @default undefined (no transformation)
|
|
63
|
+
*/
|
|
64
|
+
namespaceCase?: 'camel' | 'capital' | 'constant' | 'dot' | 'header' | 'kebab' | 'lower' | 'no' | 'param' | 'pascal' | 'path' | 'sentence' | 'snake' | 'swap' | 'title' | 'upper';
|
|
65
|
+
/**
|
|
66
|
+
* Case transformation to apply to the path segments.
|
|
67
|
+
* Available options: 'camel', 'capital', 'constant', 'dot', 'header', 'kebab',
|
|
68
|
+
* 'lower', 'no', 'param', 'pascal', 'path', 'sentence', 'snake', 'swap', 'title', 'upper'
|
|
69
|
+
* @default undefined (no transformation)
|
|
70
|
+
*/
|
|
71
|
+
pathCase?: 'camel' | 'capital' | 'constant' | 'dot' | 'header' | 'kebab' | 'lower' | 'no' | 'param' | 'pascal' | 'path' | 'sentence' | 'snake' | 'swap' | 'title' | 'upper';
|
|
72
|
+
/**
|
|
73
|
+
* Fallback namespace to use when no segments remain after filtering.
|
|
74
|
+
* Defaults to the defaultNamespace from langTagConfig.collect.defaultNamespace if not provided.
|
|
75
|
+
* @default undefined
|
|
76
|
+
*/
|
|
77
|
+
fallbackNamespace?: string;
|
|
78
|
+
/**
|
|
79
|
+
* When true and the generated namespace equals the fallback/default namespace,
|
|
80
|
+
* the namespace will be omitted from the configuration as it's redundant.
|
|
81
|
+
* @default true
|
|
82
|
+
*/
|
|
83
|
+
clearOnDefaultNamespace?: boolean;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Automatically generates namespace and path configuration based on file path structure.
|
|
87
|
+
*
|
|
88
|
+
* This algorithm analyzes the relative file path and intelligently extracts namespace
|
|
89
|
+
* and path segments according to configurable rules.
|
|
90
|
+
*
|
|
91
|
+
* @param options - Configuration options for path-based generation
|
|
92
|
+
* @returns A function compatible with LangTagCLIConfig.onConfigGeneration
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* import { pathBasedConfigGenerator } from '@lang-tag/cli/algorithms';
|
|
97
|
+
*
|
|
98
|
+
* export default {
|
|
99
|
+
* onConfigGeneration: pathBasedConfigGenerator({
|
|
100
|
+
* includeFileName: false,
|
|
101
|
+
* removeBracketedDirectories: true,
|
|
102
|
+
* ignoreDirectories: ['lib', 'utils'],
|
|
103
|
+
* ignoreIncludesRootDirectories: true, // Auto-ignores root directories from includes
|
|
104
|
+
* lowercaseNamespace: true,
|
|
105
|
+
* fallbackNamespace: 'common'
|
|
106
|
+
* })
|
|
107
|
+
* };
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function pathBasedConfigGenerator(options?: PathBasedConfigGeneratorOptions): (event: LangTagCLIConfigGenerationEvent) => Promise<void>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const path = require("pathe");
|
|
4
|
+
const caseLib = require("case");
|
|
5
|
+
function _interopNamespaceDefault(e) {
|
|
6
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
7
|
+
if (e) {
|
|
8
|
+
for (const k in e) {
|
|
9
|
+
if (k !== "default") {
|
|
10
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
11
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: () => e[k]
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
n.default = e;
|
|
19
|
+
return Object.freeze(n);
|
|
20
|
+
}
|
|
21
|
+
const caseLib__namespace = /* @__PURE__ */ _interopNamespaceDefault(caseLib);
|
|
22
|
+
function pathBasedConfigGenerator(options = {}) {
|
|
23
|
+
const {
|
|
24
|
+
includeFileName = false,
|
|
25
|
+
removeBracketedDirectories = true,
|
|
26
|
+
ignoreDirectories = [],
|
|
27
|
+
ignoreIncludesRootDirectories = false,
|
|
28
|
+
ignoreStructured = {},
|
|
29
|
+
lowercaseNamespace = false,
|
|
30
|
+
namespaceCase,
|
|
31
|
+
pathCase,
|
|
32
|
+
fallbackNamespace,
|
|
33
|
+
clearOnDefaultNamespace = true
|
|
34
|
+
} = options;
|
|
35
|
+
return async (event) => {
|
|
36
|
+
const { relativePath, langTagConfig } = event;
|
|
37
|
+
const actualFallbackNamespace = fallbackNamespace ?? langTagConfig.collect?.defaultNamespace;
|
|
38
|
+
let finalIgnoreDirectories = [...ignoreDirectories];
|
|
39
|
+
if (ignoreIncludesRootDirectories && langTagConfig.includes) {
|
|
40
|
+
const extractedDirectories = extractRootDirectoriesFromIncludes(langTagConfig.includes);
|
|
41
|
+
finalIgnoreDirectories = [.../* @__PURE__ */ new Set([...finalIgnoreDirectories, ...extractedDirectories])];
|
|
42
|
+
}
|
|
43
|
+
let pathSegments = relativePath.split(path.sep).filter(Boolean);
|
|
44
|
+
if (pathSegments.length === 0) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const fileName = pathSegments[pathSegments.length - 1];
|
|
48
|
+
const fileNameWithoutExt = fileName.replace(/\.[^.]+$/, "");
|
|
49
|
+
if (includeFileName) {
|
|
50
|
+
pathSegments[pathSegments.length - 1] = fileNameWithoutExt;
|
|
51
|
+
} else {
|
|
52
|
+
pathSegments = pathSegments.slice(0, -1);
|
|
53
|
+
}
|
|
54
|
+
pathSegments = pathSegments.map((segment) => {
|
|
55
|
+
const bracketMatch = segment.match(/^[\(\[](.+)[\)\]]$/);
|
|
56
|
+
if (bracketMatch) {
|
|
57
|
+
return removeBracketedDirectories ? null : bracketMatch[1];
|
|
58
|
+
}
|
|
59
|
+
return segment;
|
|
60
|
+
}).filter((seg) => seg !== null);
|
|
61
|
+
pathSegments = applyStructuredIgnore(pathSegments, ignoreStructured);
|
|
62
|
+
pathSegments = pathSegments.filter((seg) => !finalIgnoreDirectories.includes(seg));
|
|
63
|
+
let namespace;
|
|
64
|
+
let path$1;
|
|
65
|
+
if (pathSegments.length >= 1) {
|
|
66
|
+
namespace = pathSegments[0];
|
|
67
|
+
if (pathSegments.length > 1) {
|
|
68
|
+
path$1 = pathSegments.slice(1).join(".");
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
namespace = actualFallbackNamespace;
|
|
72
|
+
}
|
|
73
|
+
if (namespace) {
|
|
74
|
+
if (lowercaseNamespace) {
|
|
75
|
+
namespace = namespace.toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
if (namespaceCase) {
|
|
78
|
+
namespace = applyCaseTransform(namespace, namespaceCase);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (path$1 && pathCase) {
|
|
82
|
+
const pathParts = path$1.split(".");
|
|
83
|
+
const transformedParts = pathParts.map((part) => applyCaseTransform(part, pathCase));
|
|
84
|
+
path$1 = transformedParts.join(".");
|
|
85
|
+
}
|
|
86
|
+
const newConfig = {};
|
|
87
|
+
if (clearOnDefaultNamespace && namespace === actualFallbackNamespace) {
|
|
88
|
+
if (path$1) {
|
|
89
|
+
newConfig.path = path$1;
|
|
90
|
+
} else {
|
|
91
|
+
event.save(void 0);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
if (namespace) {
|
|
96
|
+
newConfig.namespace = namespace;
|
|
97
|
+
}
|
|
98
|
+
if (path$1) {
|
|
99
|
+
newConfig.path = path$1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (Object.keys(newConfig).length > 0) {
|
|
103
|
+
event.save(newConfig);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function applyStructuredIgnore(segments, structure) {
|
|
108
|
+
const result = [];
|
|
109
|
+
let currentStructure = structure;
|
|
110
|
+
for (let i = 0; i < segments.length; i++) {
|
|
111
|
+
const segment = segments[i];
|
|
112
|
+
if (segment in currentStructure) {
|
|
113
|
+
const rule = currentStructure[segment];
|
|
114
|
+
if (rule === true) {
|
|
115
|
+
currentStructure = structure;
|
|
116
|
+
continue;
|
|
117
|
+
} else if (Array.isArray(rule)) {
|
|
118
|
+
result.push(segment);
|
|
119
|
+
if (i + 1 < segments.length && rule.includes(segments[i + 1])) {
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
currentStructure = structure;
|
|
123
|
+
continue;
|
|
124
|
+
} else if (typeof rule === "object" && rule !== null) {
|
|
125
|
+
result.push(segment);
|
|
126
|
+
currentStructure = rule;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
result.push(segment);
|
|
131
|
+
currentStructure = structure;
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
function applyCaseTransform(str, caseType) {
|
|
136
|
+
const caseFunction = caseLib__namespace[caseType];
|
|
137
|
+
if (typeof caseFunction === "function") {
|
|
138
|
+
return caseFunction(str);
|
|
139
|
+
}
|
|
140
|
+
return str;
|
|
141
|
+
}
|
|
142
|
+
function extractRootDirectoriesFromIncludes(includes) {
|
|
143
|
+
const directories = /* @__PURE__ */ new Set();
|
|
144
|
+
for (const pattern of includes) {
|
|
145
|
+
let cleanPattern = pattern.replace(/^\.\//, "");
|
|
146
|
+
const match = cleanPattern.match(/^([^/]+)/);
|
|
147
|
+
if (!match) continue;
|
|
148
|
+
const firstSegment = match[1];
|
|
149
|
+
const groupMatch = firstSegment.match(/^[\(\[]([^\)\]]+)[\)\]]$/);
|
|
150
|
+
if (groupMatch) {
|
|
151
|
+
const groupDirectories = groupMatch[1].split("|").map((f) => f.trim());
|
|
152
|
+
groupDirectories.forEach((directory) => directories.add(directory));
|
|
153
|
+
} else {
|
|
154
|
+
directories.add(firstSegment);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return Array.from(directories);
|
|
158
|
+
}
|
|
159
|
+
exports.pathBasedConfigGenerator = pathBasedConfigGenerator;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predefined algorithms for lang-tag-cli configuration.
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
export { pathBasedConfigGenerator, type PathBasedConfigGeneratorOptions } from './config-generation/index.ts';
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { sep } from "pathe";
|
|
2
|
+
import * as caseLib from "case";
|
|
3
|
+
function pathBasedConfigGenerator(options = {}) {
|
|
4
|
+
const {
|
|
5
|
+
includeFileName = false,
|
|
6
|
+
removeBracketedDirectories = true,
|
|
7
|
+
ignoreDirectories = [],
|
|
8
|
+
ignoreIncludesRootDirectories = false,
|
|
9
|
+
ignoreStructured = {},
|
|
10
|
+
lowercaseNamespace = false,
|
|
11
|
+
namespaceCase,
|
|
12
|
+
pathCase,
|
|
13
|
+
fallbackNamespace,
|
|
14
|
+
clearOnDefaultNamespace = true
|
|
15
|
+
} = options;
|
|
16
|
+
return async (event) => {
|
|
17
|
+
const { relativePath, langTagConfig } = event;
|
|
18
|
+
const actualFallbackNamespace = fallbackNamespace ?? langTagConfig.collect?.defaultNamespace;
|
|
19
|
+
let finalIgnoreDirectories = [...ignoreDirectories];
|
|
20
|
+
if (ignoreIncludesRootDirectories && langTagConfig.includes) {
|
|
21
|
+
const extractedDirectories = extractRootDirectoriesFromIncludes(langTagConfig.includes);
|
|
22
|
+
finalIgnoreDirectories = [.../* @__PURE__ */ new Set([...finalIgnoreDirectories, ...extractedDirectories])];
|
|
23
|
+
}
|
|
24
|
+
let pathSegments = relativePath.split(sep).filter(Boolean);
|
|
25
|
+
if (pathSegments.length === 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const fileName = pathSegments[pathSegments.length - 1];
|
|
29
|
+
const fileNameWithoutExt = fileName.replace(/\.[^.]+$/, "");
|
|
30
|
+
if (includeFileName) {
|
|
31
|
+
pathSegments[pathSegments.length - 1] = fileNameWithoutExt;
|
|
32
|
+
} else {
|
|
33
|
+
pathSegments = pathSegments.slice(0, -1);
|
|
34
|
+
}
|
|
35
|
+
pathSegments = pathSegments.map((segment) => {
|
|
36
|
+
const bracketMatch = segment.match(/^[\(\[](.+)[\)\]]$/);
|
|
37
|
+
if (bracketMatch) {
|
|
38
|
+
return removeBracketedDirectories ? null : bracketMatch[1];
|
|
39
|
+
}
|
|
40
|
+
return segment;
|
|
41
|
+
}).filter((seg) => seg !== null);
|
|
42
|
+
pathSegments = applyStructuredIgnore(pathSegments, ignoreStructured);
|
|
43
|
+
pathSegments = pathSegments.filter((seg) => !finalIgnoreDirectories.includes(seg));
|
|
44
|
+
let namespace;
|
|
45
|
+
let path;
|
|
46
|
+
if (pathSegments.length >= 1) {
|
|
47
|
+
namespace = pathSegments[0];
|
|
48
|
+
if (pathSegments.length > 1) {
|
|
49
|
+
path = pathSegments.slice(1).join(".");
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
namespace = actualFallbackNamespace;
|
|
53
|
+
}
|
|
54
|
+
if (namespace) {
|
|
55
|
+
if (lowercaseNamespace) {
|
|
56
|
+
namespace = namespace.toLowerCase();
|
|
57
|
+
}
|
|
58
|
+
if (namespaceCase) {
|
|
59
|
+
namespace = applyCaseTransform(namespace, namespaceCase);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (path && pathCase) {
|
|
63
|
+
const pathParts = path.split(".");
|
|
64
|
+
const transformedParts = pathParts.map((part) => applyCaseTransform(part, pathCase));
|
|
65
|
+
path = transformedParts.join(".");
|
|
66
|
+
}
|
|
67
|
+
const newConfig = {};
|
|
68
|
+
if (clearOnDefaultNamespace && namespace === actualFallbackNamespace) {
|
|
69
|
+
if (path) {
|
|
70
|
+
newConfig.path = path;
|
|
71
|
+
} else {
|
|
72
|
+
event.save(void 0);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
if (namespace) {
|
|
77
|
+
newConfig.namespace = namespace;
|
|
78
|
+
}
|
|
79
|
+
if (path) {
|
|
80
|
+
newConfig.path = path;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (Object.keys(newConfig).length > 0) {
|
|
84
|
+
event.save(newConfig);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function applyStructuredIgnore(segments, structure) {
|
|
89
|
+
const result = [];
|
|
90
|
+
let currentStructure = structure;
|
|
91
|
+
for (let i = 0; i < segments.length; i++) {
|
|
92
|
+
const segment = segments[i];
|
|
93
|
+
if (segment in currentStructure) {
|
|
94
|
+
const rule = currentStructure[segment];
|
|
95
|
+
if (rule === true) {
|
|
96
|
+
currentStructure = structure;
|
|
97
|
+
continue;
|
|
98
|
+
} else if (Array.isArray(rule)) {
|
|
99
|
+
result.push(segment);
|
|
100
|
+
if (i + 1 < segments.length && rule.includes(segments[i + 1])) {
|
|
101
|
+
i++;
|
|
102
|
+
}
|
|
103
|
+
currentStructure = structure;
|
|
104
|
+
continue;
|
|
105
|
+
} else if (typeof rule === "object" && rule !== null) {
|
|
106
|
+
result.push(segment);
|
|
107
|
+
currentStructure = rule;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
result.push(segment);
|
|
112
|
+
currentStructure = structure;
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
function applyCaseTransform(str, caseType) {
|
|
117
|
+
const caseFunction = caseLib[caseType];
|
|
118
|
+
if (typeof caseFunction === "function") {
|
|
119
|
+
return caseFunction(str);
|
|
120
|
+
}
|
|
121
|
+
return str;
|
|
122
|
+
}
|
|
123
|
+
function extractRootDirectoriesFromIncludes(includes) {
|
|
124
|
+
const directories = /* @__PURE__ */ new Set();
|
|
125
|
+
for (const pattern of includes) {
|
|
126
|
+
let cleanPattern = pattern.replace(/^\.\//, "");
|
|
127
|
+
const match = cleanPattern.match(/^([^/]+)/);
|
|
128
|
+
if (!match) continue;
|
|
129
|
+
const firstSegment = match[1];
|
|
130
|
+
const groupMatch = firstSegment.match(/^[\(\[]([^\)\]]+)[\)\]]$/);
|
|
131
|
+
if (groupMatch) {
|
|
132
|
+
const groupDirectories = groupMatch[1].split("|").map((f) => f.trim());
|
|
133
|
+
groupDirectories.forEach((directory) => directories.add(directory));
|
|
134
|
+
} else {
|
|
135
|
+
directories.add(firstSegment);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return Array.from(directories);
|
|
139
|
+
}
|
|
140
|
+
export {
|
|
141
|
+
pathBasedConfigGenerator
|
|
142
|
+
};
|
package/config.d.ts
CHANGED
|
@@ -170,6 +170,7 @@ export interface LangTagCLIConfigGenerationEvent {
|
|
|
170
170
|
isImportedLibrary: boolean;
|
|
171
171
|
/** The configuration object extracted from the lang tag's options argument (e.g., `{ namespace: 'common', path: 'my.path' }`). */
|
|
172
172
|
config: LangTagTranslationsConfig | undefined;
|
|
173
|
+
langTagConfig: LangTagCLIConfig;
|
|
173
174
|
/**
|
|
174
175
|
* Tells CLI to replace tag configuration
|
|
175
176
|
* undefined = means configuration will be removed
|
package/index.cjs
CHANGED
|
@@ -441,6 +441,7 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
|
|
|
441
441
|
let newConfig = void 0;
|
|
442
442
|
let shouldUpdate = false;
|
|
443
443
|
await config.onConfigGeneration({
|
|
444
|
+
langTagConfig: config,
|
|
444
445
|
config: tag.parameterConfig,
|
|
445
446
|
absolutePath: file,
|
|
446
447
|
relativePath: path$12,
|
|
@@ -1414,13 +1415,22 @@ async function detectModuleSystem() {
|
|
|
1414
1415
|
return "cjs";
|
|
1415
1416
|
}
|
|
1416
1417
|
}
|
|
1417
|
-
function getExportStatement(moduleSystem) {
|
|
1418
|
-
return moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
|
|
1419
|
-
}
|
|
1420
1418
|
async function generateDefaultConfig() {
|
|
1421
1419
|
const moduleSystem = await detectModuleSystem();
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1420
|
+
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator } = require('@lang-tag/cli/algorithms');`;
|
|
1421
|
+
const exportStatement = moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
|
|
1422
|
+
return `${importStatement}
|
|
1423
|
+
|
|
1424
|
+
const generationAlgorithm = pathBasedConfigGenerator({
|
|
1425
|
+
ignoreIncludesRootDirectories: true,
|
|
1426
|
+
removeBracketedDirectories: true,
|
|
1427
|
+
namespaceCase: 'kebab',
|
|
1428
|
+
pathCase: 'camel',
|
|
1429
|
+
clearOnDefaultNamespace: true,
|
|
1430
|
+
ignoreDirectories: ['core', 'utils', 'helpers']
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
/** @type {import('@lang-tag/cli/config').LangTagCLIConfig} */
|
|
1424
1434
|
const config = {
|
|
1425
1435
|
tagName: 'lang',
|
|
1426
1436
|
isLibrary: false,
|
|
@@ -1431,13 +1441,9 @@ const config = {
|
|
|
1431
1441
|
// We do not modify imported configurations
|
|
1432
1442
|
if (event.isImportedLibrary) return;
|
|
1433
1443
|
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
// config.path = 'test';
|
|
1438
|
-
// config.namespace = 'testNamespace';
|
|
1439
|
-
// event.save(config);
|
|
1440
|
-
// }
|
|
1444
|
+
if (event.config?.manual) return;
|
|
1445
|
+
|
|
1446
|
+
await generationAlgorithm(event);
|
|
1441
1447
|
},
|
|
1442
1448
|
collect: {
|
|
1443
1449
|
defaultNamespace: 'common',
|
package/index.js
CHANGED
|
@@ -421,6 +421,7 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
|
421
421
|
let newConfig = void 0;
|
|
422
422
|
let shouldUpdate = false;
|
|
423
423
|
await config.onConfigGeneration({
|
|
424
|
+
langTagConfig: config,
|
|
424
425
|
config: tag.parameterConfig,
|
|
425
426
|
absolutePath: file,
|
|
426
427
|
relativePath: path2,
|
|
@@ -1394,13 +1395,22 @@ async function detectModuleSystem() {
|
|
|
1394
1395
|
return "cjs";
|
|
1395
1396
|
}
|
|
1396
1397
|
}
|
|
1397
|
-
function getExportStatement(moduleSystem) {
|
|
1398
|
-
return moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
|
|
1399
|
-
}
|
|
1400
1398
|
async function generateDefaultConfig() {
|
|
1401
1399
|
const moduleSystem = await detectModuleSystem();
|
|
1402
|
-
const
|
|
1403
|
-
|
|
1400
|
+
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator } = require('@lang-tag/cli/algorithms');`;
|
|
1401
|
+
const exportStatement = moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
|
|
1402
|
+
return `${importStatement}
|
|
1403
|
+
|
|
1404
|
+
const generationAlgorithm = pathBasedConfigGenerator({
|
|
1405
|
+
ignoreIncludesRootDirectories: true,
|
|
1406
|
+
removeBracketedDirectories: true,
|
|
1407
|
+
namespaceCase: 'kebab',
|
|
1408
|
+
pathCase: 'camel',
|
|
1409
|
+
clearOnDefaultNamespace: true,
|
|
1410
|
+
ignoreDirectories: ['core', 'utils', 'helpers']
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
/** @type {import('@lang-tag/cli/config').LangTagCLIConfig} */
|
|
1404
1414
|
const config = {
|
|
1405
1415
|
tagName: 'lang',
|
|
1406
1416
|
isLibrary: false,
|
|
@@ -1411,13 +1421,9 @@ const config = {
|
|
|
1411
1421
|
// We do not modify imported configurations
|
|
1412
1422
|
if (event.isImportedLibrary) return;
|
|
1413
1423
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
// config.path = 'test';
|
|
1418
|
-
// config.namespace = 'testNamespace';
|
|
1419
|
-
// event.save(config);
|
|
1420
|
-
// }
|
|
1424
|
+
if (event.config?.manual) return;
|
|
1425
|
+
|
|
1426
|
+
await generationAlgorithm(event);
|
|
1421
1427
|
},
|
|
1422
1428
|
collect: {
|
|
1423
1429
|
defaultNamespace: 'common',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lang-tag/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
".": {
|
|
23
23
|
"import": "./index.js",
|
|
24
24
|
"require": "./index.cjs"
|
|
25
|
+
},
|
|
26
|
+
"./algorithms": {
|
|
27
|
+
"types": "./algorithms/index.d.ts",
|
|
28
|
+
"import": "./algorithms/index.js",
|
|
29
|
+
"require": "./algorithms/index.cjs"
|
|
25
30
|
}
|
|
26
31
|
},
|
|
27
32
|
"peerDependencies": {
|
|
@@ -29,6 +34,7 @@
|
|
|
29
34
|
},
|
|
30
35
|
"dependencies": {
|
|
31
36
|
"acorn": "^8.15.0",
|
|
37
|
+
"case": "^1.6.3",
|
|
32
38
|
"chokidar": "^4.0.3",
|
|
33
39
|
"commander": "^13.1.0",
|
|
34
40
|
"globby": "^14.1.0",
|