@lang-tag/cli 0.11.1 → 0.12.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/config-generation/config-keeper.d.ts +47 -0
- package/algorithms/config-generation/index.d.ts +1 -0
- package/algorithms/index.cjs +39 -2
- package/algorithms/index.d.ts +1 -1
- package/algorithms/index.js +39 -2
- package/config.d.ts +43 -8
- package/index.cjs +39 -14
- package/index.js +39 -14
- package/package.json +1 -1
- package/template/base-app.mustache +6 -1
- package/template/base-library.mustache +6 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { LangTagCLIConfigGenerationEvent } from '../../config.ts';
|
|
2
|
+
export type ConfigKeeperMode = 'namespace' | 'path' | 'both';
|
|
3
|
+
export interface ConfigKeeperOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The name of the property in the tag configuration that indicates what should be kept.
|
|
6
|
+
* @default 'keep'
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* lang({ click: "Click" }, { namespace: 'common', path: 'button', keep: 'namespace' })
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
propertyName?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a config keeper algorithm that preserves original configuration values
|
|
16
|
+
* when they are marked to be kept using a special property (default: 'keep').
|
|
17
|
+
*
|
|
18
|
+
* This algorithm should be applied AFTER other generation algorithms to prevent
|
|
19
|
+
* them from overwriting values that should be preserved.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const pathAlgorithm = pathBasedConfigGenerator({ ... });
|
|
24
|
+
* const keeper = configKeeper();
|
|
25
|
+
*
|
|
26
|
+
* onConfigGeneration: async (event) => {
|
|
27
|
+
* // First, apply path-based generation
|
|
28
|
+
* await pathAlgorithm(event);
|
|
29
|
+
*
|
|
30
|
+
* // Then, restore any values marked to be kept
|
|
31
|
+
* await keeper(event);
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example Usage in tag:
|
|
36
|
+
* ```tsx
|
|
37
|
+
* // This will keep the namespace even if path-based algorithm tries to change it
|
|
38
|
+
* lang({ click: "Click" }, { namespace: 'common', path: 'button', keep: 'namespace' })
|
|
39
|
+
*
|
|
40
|
+
* // This will keep the path even if path-based algorithm tries to change it
|
|
41
|
+
* lang({ click: "Click" }, { namespace: 'common', path: 'old.path', keep: 'path' })
|
|
42
|
+
*
|
|
43
|
+
* // This will keep both namespace and path
|
|
44
|
+
* lang({ click: "Click" }, { namespace: 'common', path: 'button', keep: 'both' })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function configKeeper(options?: ConfigKeeperOptions): (event: LangTagCLIConfigGenerationEvent) => Promise<void>;
|
package/algorithms/index.cjs
CHANGED
|
@@ -19,6 +19,7 @@ function _interopNamespaceDefault(e) {
|
|
|
19
19
|
return Object.freeze(n);
|
|
20
20
|
}
|
|
21
21
|
const caseLib__namespace = /* @__PURE__ */ _interopNamespaceDefault(caseLib);
|
|
22
|
+
const TRIGGER_NAME$1 = "path-based-config-generator";
|
|
22
23
|
function pathBasedConfigGenerator(options = {}) {
|
|
23
24
|
const {
|
|
24
25
|
includeFileName = false,
|
|
@@ -88,7 +89,7 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
88
89
|
if (path$1) {
|
|
89
90
|
newConfig.path = path$1;
|
|
90
91
|
} else {
|
|
91
|
-
event.save(
|
|
92
|
+
event.save(null, TRIGGER_NAME$1);
|
|
92
93
|
return;
|
|
93
94
|
}
|
|
94
95
|
} else {
|
|
@@ -100,7 +101,7 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
if (Object.keys(newConfig).length > 0) {
|
|
103
|
-
event.save(newConfig);
|
|
104
|
+
event.save(newConfig, TRIGGER_NAME$1);
|
|
104
105
|
}
|
|
105
106
|
};
|
|
106
107
|
}
|
|
@@ -156,4 +157,40 @@ function extractRootDirectoriesFromIncludes(includes) {
|
|
|
156
157
|
}
|
|
157
158
|
return Array.from(directories);
|
|
158
159
|
}
|
|
160
|
+
const TRIGGER_NAME = "config-keeper";
|
|
161
|
+
function configKeeper(options = {}) {
|
|
162
|
+
const propertyName = options.propertyName ?? "keep";
|
|
163
|
+
return async (event) => {
|
|
164
|
+
if (!event.isSaved) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (!event.config) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const keepMode = event.config[propertyName];
|
|
171
|
+
if (!keepMode) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (keepMode !== "namespace" && keepMode !== "path" && keepMode !== "both") {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
let restoredConfig;
|
|
178
|
+
if (event.savedConfig === null) {
|
|
179
|
+
restoredConfig = { ...event.config };
|
|
180
|
+
delete restoredConfig.namespace;
|
|
181
|
+
delete restoredConfig.path;
|
|
182
|
+
} else {
|
|
183
|
+
restoredConfig = { ...event.savedConfig };
|
|
184
|
+
}
|
|
185
|
+
if ((keepMode === "namespace" || keepMode === "both") && event.config.namespace !== void 0) {
|
|
186
|
+
restoredConfig.namespace = event.config.namespace;
|
|
187
|
+
}
|
|
188
|
+
if ((keepMode === "path" || keepMode === "both") && event.config.path !== void 0) {
|
|
189
|
+
restoredConfig.path = event.config.path;
|
|
190
|
+
}
|
|
191
|
+
restoredConfig[propertyName] = keepMode;
|
|
192
|
+
event.save(restoredConfig, TRIGGER_NAME);
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
exports.configKeeper = configKeeper;
|
|
159
196
|
exports.pathBasedConfigGenerator = pathBasedConfigGenerator;
|
package/algorithms/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* These algorithms can be used in your lang-tag-cli config file
|
|
5
5
|
* to customize how tags are processed during collection and regeneration.
|
|
6
6
|
*/
|
|
7
|
-
export
|
|
7
|
+
export * from './config-generation/index.ts';
|
package/algorithms/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sep } from "pathe";
|
|
2
2
|
import * as caseLib from "case";
|
|
3
|
+
const TRIGGER_NAME$1 = "path-based-config-generator";
|
|
3
4
|
function pathBasedConfigGenerator(options = {}) {
|
|
4
5
|
const {
|
|
5
6
|
includeFileName = false,
|
|
@@ -69,7 +70,7 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
69
70
|
if (path) {
|
|
70
71
|
newConfig.path = path;
|
|
71
72
|
} else {
|
|
72
|
-
event.save(
|
|
73
|
+
event.save(null, TRIGGER_NAME$1);
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
75
76
|
} else {
|
|
@@ -81,7 +82,7 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
if (Object.keys(newConfig).length > 0) {
|
|
84
|
-
event.save(newConfig);
|
|
85
|
+
event.save(newConfig, TRIGGER_NAME$1);
|
|
85
86
|
}
|
|
86
87
|
};
|
|
87
88
|
}
|
|
@@ -137,6 +138,42 @@ function extractRootDirectoriesFromIncludes(includes) {
|
|
|
137
138
|
}
|
|
138
139
|
return Array.from(directories);
|
|
139
140
|
}
|
|
141
|
+
const TRIGGER_NAME = "config-keeper";
|
|
142
|
+
function configKeeper(options = {}) {
|
|
143
|
+
const propertyName = options.propertyName ?? "keep";
|
|
144
|
+
return async (event) => {
|
|
145
|
+
if (!event.isSaved) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (!event.config) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const keepMode = event.config[propertyName];
|
|
152
|
+
if (!keepMode) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (keepMode !== "namespace" && keepMode !== "path" && keepMode !== "both") {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
let restoredConfig;
|
|
159
|
+
if (event.savedConfig === null) {
|
|
160
|
+
restoredConfig = { ...event.config };
|
|
161
|
+
delete restoredConfig.namespace;
|
|
162
|
+
delete restoredConfig.path;
|
|
163
|
+
} else {
|
|
164
|
+
restoredConfig = { ...event.savedConfig };
|
|
165
|
+
}
|
|
166
|
+
if ((keepMode === "namespace" || keepMode === "both") && event.config.namespace !== void 0) {
|
|
167
|
+
restoredConfig.namespace = event.config.namespace;
|
|
168
|
+
}
|
|
169
|
+
if ((keepMode === "path" || keepMode === "both") && event.config.path !== void 0) {
|
|
170
|
+
restoredConfig.path = event.config.path;
|
|
171
|
+
}
|
|
172
|
+
restoredConfig[propertyName] = keepMode;
|
|
173
|
+
event.save(restoredConfig, TRIGGER_NAME);
|
|
174
|
+
};
|
|
175
|
+
}
|
|
140
176
|
export {
|
|
177
|
+
configKeeper,
|
|
141
178
|
pathBasedConfigGenerator
|
|
142
179
|
};
|
package/config.d.ts
CHANGED
|
@@ -99,9 +99,28 @@ export interface LangTagCLIConfig {
|
|
|
99
99
|
* Allows dynamic modification of the tag's configuration (namespace, path, etc.)
|
|
100
100
|
* based on the file path or other context.
|
|
101
101
|
*
|
|
102
|
+
* **IMPORTANT:** The `event.config` object is deeply frozen and immutable. Any attempt
|
|
103
|
+
* to directly modify it will throw an error. To update the configuration, you must
|
|
104
|
+
* use `event.save(newConfig)` with a new configuration object.
|
|
105
|
+
*
|
|
102
106
|
* Changes made inside this function are **applied only if you explicitly call**
|
|
103
107
|
* `event.save(configuration)`. Returning a value or modifying the event object
|
|
104
108
|
* without calling `save()` will **not** update the configuration.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* onConfigGeneration: async (event) => {
|
|
113
|
+
* // ❌ This will throw an error:
|
|
114
|
+
* // event.config.namespace = "new-namespace";
|
|
115
|
+
*
|
|
116
|
+
* // ✅ Correct way to update:
|
|
117
|
+
* event.save({
|
|
118
|
+
* ...event.config,
|
|
119
|
+
* namespace: "new-namespace",
|
|
120
|
+
* path: "new.path"
|
|
121
|
+
* });
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
105
124
|
*/
|
|
106
125
|
onConfigGeneration: (event: LangTagCLIConfigGenerationEvent) => Promise<void>;
|
|
107
126
|
debug?: boolean;
|
|
@@ -163,19 +182,35 @@ export interface LangTagCLIConflict {
|
|
|
163
182
|
}
|
|
164
183
|
export interface LangTagCLIConfigGenerationEvent {
|
|
165
184
|
/** The absolute path to the source file being processed. */
|
|
166
|
-
absolutePath: string;
|
|
185
|
+
readonly absolutePath: string;
|
|
167
186
|
/** The path of the source file relative to the project root (where the command was invoked). */
|
|
168
|
-
relativePath: string;
|
|
187
|
+
readonly relativePath: string;
|
|
169
188
|
/** True if the file being processed is located within the configured library import directory (`config.import.dir`). */
|
|
170
|
-
isImportedLibrary: boolean;
|
|
171
|
-
/**
|
|
172
|
-
|
|
173
|
-
|
|
189
|
+
readonly isImportedLibrary: boolean;
|
|
190
|
+
/**
|
|
191
|
+
* The configuration object extracted from the lang tag's options argument (e.g., `{ namespace: 'common', path: 'my.path' }`).
|
|
192
|
+
*
|
|
193
|
+
* **This object is deeply frozen and immutable.** Any attempt to modify it will throw an error in strict mode.
|
|
194
|
+
* To update the configuration, use the `save()` method with a new configuration object.
|
|
195
|
+
*/
|
|
196
|
+
readonly config: Readonly<LangTagTranslationsConfig> | undefined;
|
|
197
|
+
readonly langTagConfig: LangTagCLIConfig;
|
|
198
|
+
/**
|
|
199
|
+
* Indicates whether the `save()` method has been called during this event.
|
|
200
|
+
*/
|
|
201
|
+
readonly isSaved: boolean;
|
|
202
|
+
/**
|
|
203
|
+
* The updated configuration that was passed to the `save()` method.
|
|
204
|
+
* - `undefined` if `save()` has not been called yet
|
|
205
|
+
* - `null` if `save(null)` was called to remove the configuration
|
|
206
|
+
* - `LangTagTranslationsConfig` object if a new configuration was saved
|
|
207
|
+
*/
|
|
208
|
+
readonly savedConfig: LangTagTranslationsConfig | null | undefined;
|
|
174
209
|
/**
|
|
175
210
|
* Tells CLI to replace tag configuration
|
|
176
|
-
*
|
|
211
|
+
* null = means configuration will be removed
|
|
177
212
|
**/
|
|
178
|
-
save(config: LangTagTranslationsConfig |
|
|
213
|
+
save(config: LangTagTranslationsConfig | null, triggerName?: string): void;
|
|
179
214
|
}
|
|
180
215
|
export interface LangTagCLICollectConfigFixEvent {
|
|
181
216
|
config: LangTagTranslationsConfig;
|
package/index.cjs
CHANGED
|
@@ -168,7 +168,7 @@ class $LT_TagProcessor {
|
|
|
168
168
|
replaceTags(fileContent, replacements) {
|
|
169
169
|
const replaceMap = /* @__PURE__ */ new Map();
|
|
170
170
|
replacements.forEach((R) => {
|
|
171
|
-
if (!R.translations && !R.config) {
|
|
171
|
+
if (!R.translations && !R.config && R.config !== null) {
|
|
172
172
|
throw new Error("Replacement data is required!");
|
|
173
173
|
}
|
|
174
174
|
const tag = R.tag;
|
|
@@ -182,7 +182,7 @@ class $LT_TagProcessor {
|
|
|
182
182
|
throw new Error(`Tag translations are invalid object! Translations: ${newTranslationsString}`);
|
|
183
183
|
}
|
|
184
184
|
let newConfigString = R.config;
|
|
185
|
-
if (!newConfigString) newConfigString = tag.parameterConfig;
|
|
185
|
+
if (!newConfigString && newConfigString !== null) newConfigString = tag.parameterConfig;
|
|
186
186
|
if (newConfigString) {
|
|
187
187
|
try {
|
|
188
188
|
if (typeof newConfigString === "string") JSON5.parse(newConfigString);
|
|
@@ -426,6 +426,16 @@ function $LT_FilterEmptyNamespaceTags(tags, logger) {
|
|
|
426
426
|
return true;
|
|
427
427
|
});
|
|
428
428
|
}
|
|
429
|
+
function deepFreezeObject(obj) {
|
|
430
|
+
const propNames = Object.getOwnPropertyNames(obj);
|
|
431
|
+
for (const name of propNames) {
|
|
432
|
+
const value = obj[name];
|
|
433
|
+
if (value && typeof value === "object") {
|
|
434
|
+
deepFreezeObject(value);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return Object.freeze(obj);
|
|
438
|
+
}
|
|
429
439
|
async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
|
|
430
440
|
let libraryImportsDir = config.import.dir;
|
|
431
441
|
if (!libraryImportsDir.endsWith(path.sep)) libraryImportsDir += path.sep;
|
|
@@ -437,34 +447,50 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
|
|
|
437
447
|
return false;
|
|
438
448
|
}
|
|
439
449
|
const replacements = [];
|
|
450
|
+
let lastUpdatedLine = 0;
|
|
440
451
|
for (let tag of tags) {
|
|
441
452
|
let newConfig = void 0;
|
|
442
453
|
let shouldUpdate = false;
|
|
443
|
-
|
|
454
|
+
const frozenConfig = tag.parameterConfig ? deepFreezeObject(tag.parameterConfig) : tag.parameterConfig;
|
|
455
|
+
const event = {
|
|
444
456
|
langTagConfig: config,
|
|
445
|
-
config:
|
|
457
|
+
config: frozenConfig,
|
|
446
458
|
absolutePath: file,
|
|
447
459
|
relativePath: path$12,
|
|
448
460
|
isImportedLibrary: path$12.startsWith(libraryImportsDir),
|
|
449
|
-
|
|
461
|
+
isSaved: false,
|
|
462
|
+
savedConfig: void 0,
|
|
463
|
+
save: (updatedConfig, triggerName) => {
|
|
464
|
+
if (!updatedConfig && updatedConfig !== null) throw new Error("Wrong config data");
|
|
450
465
|
newConfig = updatedConfig;
|
|
451
466
|
shouldUpdate = true;
|
|
467
|
+
event.isSaved = true;
|
|
468
|
+
event.savedConfig = updatedConfig;
|
|
469
|
+
logger.debug('Called save for "{path}" with config "{config}" triggered by: ("{trigger}")', { path: path$12, config: JSON.stringify(updatedConfig), trigger: triggerName || "-" });
|
|
452
470
|
}
|
|
453
|
-
}
|
|
471
|
+
};
|
|
472
|
+
await config.onConfigGeneration(event);
|
|
454
473
|
if (!shouldUpdate) {
|
|
455
474
|
continue;
|
|
456
475
|
}
|
|
457
|
-
|
|
476
|
+
lastUpdatedLine = tag.line;
|
|
477
|
+
if (!isConfigSame(tag.parameterConfig, newConfig)) {
|
|
458
478
|
replacements.push({ tag, config: newConfig });
|
|
459
479
|
}
|
|
460
480
|
}
|
|
461
481
|
if (replacements.length) {
|
|
462
482
|
const newContent = processor.replaceTags(fileContent, replacements);
|
|
463
483
|
await promises.writeFile(file, newContent, "utf-8");
|
|
484
|
+
logger.info('Lang tag configurations written for file "{path}" (file://{file}:{line})', { path: path$12, file, line: lastUpdatedLine });
|
|
464
485
|
return true;
|
|
465
486
|
}
|
|
466
487
|
return false;
|
|
467
488
|
}
|
|
489
|
+
function isConfigSame(c1, c2) {
|
|
490
|
+
if (!c1 && !c2) return true;
|
|
491
|
+
if (c1 && typeof c1 === "object" && c2 && typeof c2 === "object" && JSON5.stringify(c1) === JSON5.stringify(c2)) return true;
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
468
494
|
const CONFIG_FILE_NAME = ".lang-tag.config.js";
|
|
469
495
|
const EXPORTS_FILE_NAME = ".lang-tag.exports.json";
|
|
470
496
|
const LANG_TAG_DEFAULT_CONFIG = {
|
|
@@ -983,7 +1009,6 @@ async function $LT_CMD_RegenerateTags() {
|
|
|
983
1009
|
const path2 = file.substring(charactersToSkip);
|
|
984
1010
|
const localDirty = await checkAndRegenerateFileLangTags(config, logger, file, path2);
|
|
985
1011
|
if (localDirty) {
|
|
986
|
-
logger.info('Lang tag configurations written for file "{path}"', { path: path2 });
|
|
987
1012
|
dirty = true;
|
|
988
1013
|
}
|
|
989
1014
|
}
|
|
@@ -1384,10 +1409,7 @@ async function handleFile(config, logger, cwdRelativeFilePath, event) {
|
|
|
1384
1409
|
}
|
|
1385
1410
|
const cwd = process.cwd();
|
|
1386
1411
|
const absoluteFilePath = path.join(cwd, cwdRelativeFilePath);
|
|
1387
|
-
|
|
1388
|
-
if (dirty) {
|
|
1389
|
-
logger.info(`Lang tag configurations written for file "{filePath}"`, { filePath: cwdRelativeFilePath });
|
|
1390
|
-
}
|
|
1412
|
+
await checkAndRegenerateFileLangTags(config, logger, absoluteFilePath, cwdRelativeFilePath);
|
|
1391
1413
|
const files = await $LT_CollectCandidateFilesWithTags({ filesToScan: [cwdRelativeFilePath], config, logger });
|
|
1392
1414
|
const namespaces = await $LT_GroupTagsToNamespaces({ logger, files, config });
|
|
1393
1415
|
const changedNamespaces = await $LT_WriteToNamespaces({ config, namespaces, logger });
|
|
@@ -1417,7 +1439,7 @@ async function detectModuleSystem() {
|
|
|
1417
1439
|
}
|
|
1418
1440
|
async function generateDefaultConfig() {
|
|
1419
1441
|
const moduleSystem = await detectModuleSystem();
|
|
1420
|
-
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator } = require('@lang-tag/cli/algorithms');`;
|
|
1442
|
+
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator, configKeeper } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator, configKeeper } = require('@lang-tag/cli/algorithms');`;
|
|
1421
1443
|
const exportStatement = moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
|
|
1422
1444
|
return `${importStatement}
|
|
1423
1445
|
|
|
@@ -1429,6 +1451,7 @@ const generationAlgorithm = pathBasedConfigGenerator({
|
|
|
1429
1451
|
clearOnDefaultNamespace: true,
|
|
1430
1452
|
ignoreDirectories: ['core', 'utils', 'helpers']
|
|
1431
1453
|
});
|
|
1454
|
+
const keeper = configKeeper();
|
|
1432
1455
|
|
|
1433
1456
|
/** @type {import('@lang-tag/cli/config').LangTagCLIConfig} */
|
|
1434
1457
|
const config = {
|
|
@@ -1441,9 +1464,11 @@ const config = {
|
|
|
1441
1464
|
// We do not modify imported configurations
|
|
1442
1465
|
if (event.isImportedLibrary) return;
|
|
1443
1466
|
|
|
1444
|
-
if (event.config?.
|
|
1467
|
+
if (event.config?.keep === 'both') return;
|
|
1445
1468
|
|
|
1446
1469
|
await generationAlgorithm(event);
|
|
1470
|
+
|
|
1471
|
+
await keeper(event);
|
|
1447
1472
|
},
|
|
1448
1473
|
collect: {
|
|
1449
1474
|
defaultNamespace: 'common',
|
package/index.js
CHANGED
|
@@ -148,7 +148,7 @@ class $LT_TagProcessor {
|
|
|
148
148
|
replaceTags(fileContent, replacements) {
|
|
149
149
|
const replaceMap = /* @__PURE__ */ new Map();
|
|
150
150
|
replacements.forEach((R) => {
|
|
151
|
-
if (!R.translations && !R.config) {
|
|
151
|
+
if (!R.translations && !R.config && R.config !== null) {
|
|
152
152
|
throw new Error("Replacement data is required!");
|
|
153
153
|
}
|
|
154
154
|
const tag = R.tag;
|
|
@@ -162,7 +162,7 @@ class $LT_TagProcessor {
|
|
|
162
162
|
throw new Error(`Tag translations are invalid object! Translations: ${newTranslationsString}`);
|
|
163
163
|
}
|
|
164
164
|
let newConfigString = R.config;
|
|
165
|
-
if (!newConfigString) newConfigString = tag.parameterConfig;
|
|
165
|
+
if (!newConfigString && newConfigString !== null) newConfigString = tag.parameterConfig;
|
|
166
166
|
if (newConfigString) {
|
|
167
167
|
try {
|
|
168
168
|
if (typeof newConfigString === "string") JSON5.parse(newConfigString);
|
|
@@ -406,6 +406,16 @@ function $LT_FilterEmptyNamespaceTags(tags, logger) {
|
|
|
406
406
|
return true;
|
|
407
407
|
});
|
|
408
408
|
}
|
|
409
|
+
function deepFreezeObject(obj) {
|
|
410
|
+
const propNames = Object.getOwnPropertyNames(obj);
|
|
411
|
+
for (const name of propNames) {
|
|
412
|
+
const value = obj[name];
|
|
413
|
+
if (value && typeof value === "object") {
|
|
414
|
+
deepFreezeObject(value);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return Object.freeze(obj);
|
|
418
|
+
}
|
|
409
419
|
async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
410
420
|
let libraryImportsDir = config.import.dir;
|
|
411
421
|
if (!libraryImportsDir.endsWith(sep)) libraryImportsDir += sep;
|
|
@@ -417,34 +427,50 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
|
417
427
|
return false;
|
|
418
428
|
}
|
|
419
429
|
const replacements = [];
|
|
430
|
+
let lastUpdatedLine = 0;
|
|
420
431
|
for (let tag of tags) {
|
|
421
432
|
let newConfig = void 0;
|
|
422
433
|
let shouldUpdate = false;
|
|
423
|
-
|
|
434
|
+
const frozenConfig = tag.parameterConfig ? deepFreezeObject(tag.parameterConfig) : tag.parameterConfig;
|
|
435
|
+
const event = {
|
|
424
436
|
langTagConfig: config,
|
|
425
|
-
config:
|
|
437
|
+
config: frozenConfig,
|
|
426
438
|
absolutePath: file,
|
|
427
439
|
relativePath: path2,
|
|
428
440
|
isImportedLibrary: path2.startsWith(libraryImportsDir),
|
|
429
|
-
|
|
441
|
+
isSaved: false,
|
|
442
|
+
savedConfig: void 0,
|
|
443
|
+
save: (updatedConfig, triggerName) => {
|
|
444
|
+
if (!updatedConfig && updatedConfig !== null) throw new Error("Wrong config data");
|
|
430
445
|
newConfig = updatedConfig;
|
|
431
446
|
shouldUpdate = true;
|
|
447
|
+
event.isSaved = true;
|
|
448
|
+
event.savedConfig = updatedConfig;
|
|
449
|
+
logger.debug('Called save for "{path}" with config "{config}" triggered by: ("{trigger}")', { path: path2, config: JSON.stringify(updatedConfig), trigger: triggerName || "-" });
|
|
432
450
|
}
|
|
433
|
-
}
|
|
451
|
+
};
|
|
452
|
+
await config.onConfigGeneration(event);
|
|
434
453
|
if (!shouldUpdate) {
|
|
435
454
|
continue;
|
|
436
455
|
}
|
|
437
|
-
|
|
456
|
+
lastUpdatedLine = tag.line;
|
|
457
|
+
if (!isConfigSame(tag.parameterConfig, newConfig)) {
|
|
438
458
|
replacements.push({ tag, config: newConfig });
|
|
439
459
|
}
|
|
440
460
|
}
|
|
441
461
|
if (replacements.length) {
|
|
442
462
|
const newContent = processor.replaceTags(fileContent, replacements);
|
|
443
463
|
await writeFile(file, newContent, "utf-8");
|
|
464
|
+
logger.info('Lang tag configurations written for file "{path}" (file://{file}:{line})', { path: path2, file, line: lastUpdatedLine });
|
|
444
465
|
return true;
|
|
445
466
|
}
|
|
446
467
|
return false;
|
|
447
468
|
}
|
|
469
|
+
function isConfigSame(c1, c2) {
|
|
470
|
+
if (!c1 && !c2) return true;
|
|
471
|
+
if (c1 && typeof c1 === "object" && c2 && typeof c2 === "object" && JSON5.stringify(c1) === JSON5.stringify(c2)) return true;
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
448
474
|
const CONFIG_FILE_NAME = ".lang-tag.config.js";
|
|
449
475
|
const EXPORTS_FILE_NAME = ".lang-tag.exports.json";
|
|
450
476
|
const LANG_TAG_DEFAULT_CONFIG = {
|
|
@@ -963,7 +989,6 @@ async function $LT_CMD_RegenerateTags() {
|
|
|
963
989
|
const path2 = file.substring(charactersToSkip);
|
|
964
990
|
const localDirty = await checkAndRegenerateFileLangTags(config, logger, file, path2);
|
|
965
991
|
if (localDirty) {
|
|
966
|
-
logger.info('Lang tag configurations written for file "{path}"', { path: path2 });
|
|
967
992
|
dirty = true;
|
|
968
993
|
}
|
|
969
994
|
}
|
|
@@ -1364,10 +1389,7 @@ async function handleFile(config, logger, cwdRelativeFilePath, event) {
|
|
|
1364
1389
|
}
|
|
1365
1390
|
const cwd = process.cwd();
|
|
1366
1391
|
const absoluteFilePath = path__default.join(cwd, cwdRelativeFilePath);
|
|
1367
|
-
|
|
1368
|
-
if (dirty) {
|
|
1369
|
-
logger.info(`Lang tag configurations written for file "{filePath}"`, { filePath: cwdRelativeFilePath });
|
|
1370
|
-
}
|
|
1392
|
+
await checkAndRegenerateFileLangTags(config, logger, absoluteFilePath, cwdRelativeFilePath);
|
|
1371
1393
|
const files = await $LT_CollectCandidateFilesWithTags({ filesToScan: [cwdRelativeFilePath], config, logger });
|
|
1372
1394
|
const namespaces = await $LT_GroupTagsToNamespaces({ logger, files, config });
|
|
1373
1395
|
const changedNamespaces = await $LT_WriteToNamespaces({ config, namespaces, logger });
|
|
@@ -1397,7 +1419,7 @@ async function detectModuleSystem() {
|
|
|
1397
1419
|
}
|
|
1398
1420
|
async function generateDefaultConfig() {
|
|
1399
1421
|
const moduleSystem = await detectModuleSystem();
|
|
1400
|
-
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator } = require('@lang-tag/cli/algorithms');`;
|
|
1422
|
+
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator, configKeeper } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator, configKeeper } = require('@lang-tag/cli/algorithms');`;
|
|
1401
1423
|
const exportStatement = moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
|
|
1402
1424
|
return `${importStatement}
|
|
1403
1425
|
|
|
@@ -1409,6 +1431,7 @@ const generationAlgorithm = pathBasedConfigGenerator({
|
|
|
1409
1431
|
clearOnDefaultNamespace: true,
|
|
1410
1432
|
ignoreDirectories: ['core', 'utils', 'helpers']
|
|
1411
1433
|
});
|
|
1434
|
+
const keeper = configKeeper();
|
|
1412
1435
|
|
|
1413
1436
|
/** @type {import('@lang-tag/cli/config').LangTagCLIConfig} */
|
|
1414
1437
|
const config = {
|
|
@@ -1421,9 +1444,11 @@ const config = {
|
|
|
1421
1444
|
// We do not modify imported configurations
|
|
1422
1445
|
if (event.isImportedLibrary) return;
|
|
1423
1446
|
|
|
1424
|
-
if (event.config?.
|
|
1447
|
+
if (event.config?.keep === 'both') return;
|
|
1425
1448
|
|
|
1426
1449
|
await generationAlgorithm(event);
|
|
1450
|
+
|
|
1451
|
+
await keeper(event);
|
|
1427
1452
|
},
|
|
1428
1453
|
collect: {
|
|
1429
1454
|
defaultNamespace: 'common',
|
package/package.json
CHANGED
|
@@ -13,9 +13,14 @@ import { createCallableTranslations } from 'lang-tag';
|
|
|
13
13
|
import { ReactNode, useMemo } from 'react';
|
|
14
14
|
{{/isReact}}
|
|
15
15
|
|
|
16
|
+
{{#isTypeScript}}
|
|
17
|
+
interface TagConfig extends LangTagTranslationsConfig {
|
|
18
|
+
keep?: 'namespace' | 'path' | 'both'
|
|
19
|
+
}
|
|
20
|
+
{{/isTypeScript}}
|
|
16
21
|
export function {{tagName}}{{#isTypeScript}}<T extends LangTagTranslations>{{/isTypeScript}}(
|
|
17
22
|
baseTranslations{{#isTypeScript}}: T{{/isTypeScript}},
|
|
18
|
-
config{{#isTypeScript}}?:
|
|
23
|
+
config{{#isTypeScript}}?: TagConfig{{/isTypeScript}},
|
|
19
24
|
) {
|
|
20
25
|
// Example integration with react-i18next:
|
|
21
26
|
// const namespace = config?.namespace || '';
|
|
@@ -21,9 +21,14 @@ import {
|
|
|
21
21
|
} from 'react';
|
|
22
22
|
{{/isReact}}
|
|
23
23
|
|
|
24
|
+
{{#isTypeScript}}
|
|
25
|
+
interface TagConfig extends LangTagTranslationsConfig {
|
|
26
|
+
keep?: 'namespace' | 'path' | 'both'
|
|
27
|
+
}
|
|
28
|
+
{{/isTypeScript}}
|
|
24
29
|
export function {{tagName}}<T extends LangTagTranslations>(
|
|
25
30
|
baseTranslations: T,
|
|
26
|
-
config?:
|
|
31
|
+
config?: TagConfig,
|
|
27
32
|
) {
|
|
28
33
|
const createTranslationHelper = (normalized{{#isTypeScript}}: CallableTranslations<T> | null{{/isTypeScript}}) =>
|
|
29
34
|
createCallableTranslations(baseTranslations, config, {
|