@lang-tag/cli 0.11.2 → 0.12.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/algorithms/config-generation/config-keeper.d.ts +47 -0
- package/algorithms/config-generation/index.d.ts +1 -0
- package/algorithms/index.cjs +50 -4
- package/algorithms/index.d.ts +1 -1
- package/algorithms/index.js +50 -4
- package/config.d.ts +13 -2
- package/index.cjs +43 -26
- package/index.js +43 -26
- package/package.json +2 -2
- 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,
|
|
@@ -83,13 +84,22 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
83
84
|
const transformedParts = pathParts.map((part) => applyCaseTransform(part, pathCase));
|
|
84
85
|
path$1 = transformedParts.join(".");
|
|
85
86
|
}
|
|
86
|
-
const newConfig = {};
|
|
87
|
+
const newConfig = event.config ? { ...event.config } : {};
|
|
87
88
|
if (clearOnDefaultNamespace && namespace === actualFallbackNamespace) {
|
|
88
89
|
if (path$1) {
|
|
89
90
|
newConfig.path = path$1;
|
|
91
|
+
delete newConfig.namespace;
|
|
90
92
|
} else {
|
|
91
|
-
event.
|
|
92
|
-
|
|
93
|
+
const hasOtherProperties = event.config && Object.keys(event.config).some(
|
|
94
|
+
(key) => key !== "namespace" && key !== "path"
|
|
95
|
+
);
|
|
96
|
+
if (!hasOtherProperties) {
|
|
97
|
+
event.save(null, TRIGGER_NAME$1);
|
|
98
|
+
return;
|
|
99
|
+
} else {
|
|
100
|
+
delete newConfig.namespace;
|
|
101
|
+
delete newConfig.path;
|
|
102
|
+
}
|
|
93
103
|
}
|
|
94
104
|
} else {
|
|
95
105
|
if (namespace) {
|
|
@@ -100,7 +110,7 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
112
|
if (Object.keys(newConfig).length > 0) {
|
|
103
|
-
event.save(newConfig);
|
|
113
|
+
event.save(newConfig, TRIGGER_NAME$1);
|
|
104
114
|
}
|
|
105
115
|
};
|
|
106
116
|
}
|
|
@@ -156,4 +166,40 @@ function extractRootDirectoriesFromIncludes(includes) {
|
|
|
156
166
|
}
|
|
157
167
|
return Array.from(directories);
|
|
158
168
|
}
|
|
169
|
+
const TRIGGER_NAME = "config-keeper";
|
|
170
|
+
function configKeeper(options = {}) {
|
|
171
|
+
const propertyName = options.propertyName ?? "keep";
|
|
172
|
+
return async (event) => {
|
|
173
|
+
if (!event.isSaved) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!event.config) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const keepMode = event.config[propertyName];
|
|
180
|
+
if (!keepMode) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (keepMode !== "namespace" && keepMode !== "path" && keepMode !== "both") {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
let restoredConfig;
|
|
187
|
+
if (event.savedConfig === null) {
|
|
188
|
+
restoredConfig = { ...event.config };
|
|
189
|
+
delete restoredConfig.namespace;
|
|
190
|
+
delete restoredConfig.path;
|
|
191
|
+
} else {
|
|
192
|
+
restoredConfig = { ...event.savedConfig };
|
|
193
|
+
}
|
|
194
|
+
if ((keepMode === "namespace" || keepMode === "both") && event.config.namespace !== void 0) {
|
|
195
|
+
restoredConfig.namespace = event.config.namespace;
|
|
196
|
+
}
|
|
197
|
+
if ((keepMode === "path" || keepMode === "both") && event.config.path !== void 0) {
|
|
198
|
+
restoredConfig.path = event.config.path;
|
|
199
|
+
}
|
|
200
|
+
restoredConfig[propertyName] = keepMode;
|
|
201
|
+
event.save(restoredConfig, TRIGGER_NAME);
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
exports.configKeeper = configKeeper;
|
|
159
205
|
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,
|
|
@@ -64,13 +65,22 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
64
65
|
const transformedParts = pathParts.map((part) => applyCaseTransform(part, pathCase));
|
|
65
66
|
path = transformedParts.join(".");
|
|
66
67
|
}
|
|
67
|
-
const newConfig = {};
|
|
68
|
+
const newConfig = event.config ? { ...event.config } : {};
|
|
68
69
|
if (clearOnDefaultNamespace && namespace === actualFallbackNamespace) {
|
|
69
70
|
if (path) {
|
|
70
71
|
newConfig.path = path;
|
|
72
|
+
delete newConfig.namespace;
|
|
71
73
|
} else {
|
|
72
|
-
event.
|
|
73
|
-
|
|
74
|
+
const hasOtherProperties = event.config && Object.keys(event.config).some(
|
|
75
|
+
(key) => key !== "namespace" && key !== "path"
|
|
76
|
+
);
|
|
77
|
+
if (!hasOtherProperties) {
|
|
78
|
+
event.save(null, TRIGGER_NAME$1);
|
|
79
|
+
return;
|
|
80
|
+
} else {
|
|
81
|
+
delete newConfig.namespace;
|
|
82
|
+
delete newConfig.path;
|
|
83
|
+
}
|
|
74
84
|
}
|
|
75
85
|
} else {
|
|
76
86
|
if (namespace) {
|
|
@@ -81,7 +91,7 @@ function pathBasedConfigGenerator(options = {}) {
|
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
93
|
if (Object.keys(newConfig).length > 0) {
|
|
84
|
-
event.save(newConfig);
|
|
94
|
+
event.save(newConfig, TRIGGER_NAME$1);
|
|
85
95
|
}
|
|
86
96
|
};
|
|
87
97
|
}
|
|
@@ -137,6 +147,42 @@ function extractRootDirectoriesFromIncludes(includes) {
|
|
|
137
147
|
}
|
|
138
148
|
return Array.from(directories);
|
|
139
149
|
}
|
|
150
|
+
const TRIGGER_NAME = "config-keeper";
|
|
151
|
+
function configKeeper(options = {}) {
|
|
152
|
+
const propertyName = options.propertyName ?? "keep";
|
|
153
|
+
return async (event) => {
|
|
154
|
+
if (!event.isSaved) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!event.config) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const keepMode = event.config[propertyName];
|
|
161
|
+
if (!keepMode) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (keepMode !== "namespace" && keepMode !== "path" && keepMode !== "both") {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
let restoredConfig;
|
|
168
|
+
if (event.savedConfig === null) {
|
|
169
|
+
restoredConfig = { ...event.config };
|
|
170
|
+
delete restoredConfig.namespace;
|
|
171
|
+
delete restoredConfig.path;
|
|
172
|
+
} else {
|
|
173
|
+
restoredConfig = { ...event.savedConfig };
|
|
174
|
+
}
|
|
175
|
+
if ((keepMode === "namespace" || keepMode === "both") && event.config.namespace !== void 0) {
|
|
176
|
+
restoredConfig.namespace = event.config.namespace;
|
|
177
|
+
}
|
|
178
|
+
if ((keepMode === "path" || keepMode === "both") && event.config.path !== void 0) {
|
|
179
|
+
restoredConfig.path = event.config.path;
|
|
180
|
+
}
|
|
181
|
+
restoredConfig[propertyName] = keepMode;
|
|
182
|
+
event.save(restoredConfig, TRIGGER_NAME);
|
|
183
|
+
};
|
|
184
|
+
}
|
|
140
185
|
export {
|
|
186
|
+
configKeeper,
|
|
141
187
|
pathBasedConfigGenerator
|
|
142
188
|
};
|
package/config.d.ts
CHANGED
|
@@ -195,11 +195,22 @@ export interface LangTagCLIConfigGenerationEvent {
|
|
|
195
195
|
*/
|
|
196
196
|
readonly config: Readonly<LangTagTranslationsConfig> | undefined;
|
|
197
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;
|
|
198
209
|
/**
|
|
199
210
|
* Tells CLI to replace tag configuration
|
|
200
|
-
*
|
|
211
|
+
* null = means configuration will be removed
|
|
201
212
|
**/
|
|
202
|
-
save(config: LangTagTranslationsConfig |
|
|
213
|
+
save(config: LangTagTranslationsConfig | null, triggerName?: string): void;
|
|
203
214
|
}
|
|
204
215
|
export interface LangTagCLICollectConfigFixEvent {
|
|
205
216
|
config: LangTagTranslationsConfig;
|
package/index.cjs
CHANGED
|
@@ -173,7 +173,7 @@ class $LT_TagProcessor {
|
|
|
173
173
|
}
|
|
174
174
|
const tag = R.tag;
|
|
175
175
|
let newTranslationsString = R.translations;
|
|
176
|
-
if (!newTranslationsString) newTranslationsString = this.config.translationArgPosition === 1 ? tag.parameter1Text : tag.parameter2Text;
|
|
176
|
+
if (!newTranslationsString) newTranslationsString = this.config.translationArgPosition === 1 ? tag.parameter1Text : tag.parameter2Text || "{}";
|
|
177
177
|
else if (typeof newTranslationsString !== "string") newTranslationsString = JSON5.stringify(newTranslationsString);
|
|
178
178
|
if (!newTranslationsString) throw new Error("Tag must have translations provided!");
|
|
179
179
|
try {
|
|
@@ -191,6 +191,9 @@ class $LT_TagProcessor {
|
|
|
191
191
|
throw new Error(`Tag config is invalid object! Config: ${newConfigString}`);
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
+
if (newConfigString === null && this.config.translationArgPosition === 2) {
|
|
195
|
+
newConfigString = "{}";
|
|
196
|
+
}
|
|
194
197
|
const arg1 = this.config.translationArgPosition === 1 ? newTranslationsString : newConfigString;
|
|
195
198
|
const arg2 = this.config.translationArgPosition === 1 ? newConfigString : newTranslationsString;
|
|
196
199
|
let tagFunction = `${this.config.tagName}(${arg1}`;
|
|
@@ -447,35 +450,50 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
|
|
|
447
450
|
return false;
|
|
448
451
|
}
|
|
449
452
|
const replacements = [];
|
|
453
|
+
let lastUpdatedLine = 0;
|
|
450
454
|
for (let tag of tags) {
|
|
451
455
|
let newConfig = void 0;
|
|
452
456
|
let shouldUpdate = false;
|
|
453
457
|
const frozenConfig = tag.parameterConfig ? deepFreezeObject(tag.parameterConfig) : tag.parameterConfig;
|
|
454
|
-
|
|
458
|
+
const event = {
|
|
455
459
|
langTagConfig: config,
|
|
456
460
|
config: frozenConfig,
|
|
457
461
|
absolutePath: file,
|
|
458
462
|
relativePath: path$12,
|
|
459
463
|
isImportedLibrary: path$12.startsWith(libraryImportsDir),
|
|
460
|
-
|
|
461
|
-
|
|
464
|
+
isSaved: false,
|
|
465
|
+
savedConfig: void 0,
|
|
466
|
+
save: (updatedConfig, triggerName) => {
|
|
467
|
+
if (!updatedConfig && updatedConfig !== null) throw new Error("Wrong config data");
|
|
468
|
+
newConfig = updatedConfig;
|
|
462
469
|
shouldUpdate = true;
|
|
470
|
+
event.isSaved = true;
|
|
471
|
+
event.savedConfig = updatedConfig;
|
|
472
|
+
logger.debug('Called save for "{path}" with config "{config}" triggered by: ("{trigger}")', { path: path$12, config: JSON.stringify(updatedConfig), trigger: triggerName || "-" });
|
|
463
473
|
}
|
|
464
|
-
}
|
|
474
|
+
};
|
|
475
|
+
await config.onConfigGeneration(event);
|
|
465
476
|
if (!shouldUpdate) {
|
|
466
477
|
continue;
|
|
467
478
|
}
|
|
468
|
-
|
|
479
|
+
lastUpdatedLine = tag.line;
|
|
480
|
+
if (!isConfigSame(tag.parameterConfig, newConfig)) {
|
|
469
481
|
replacements.push({ tag, config: newConfig });
|
|
470
482
|
}
|
|
471
483
|
}
|
|
472
484
|
if (replacements.length) {
|
|
473
485
|
const newContent = processor.replaceTags(fileContent, replacements);
|
|
474
486
|
await promises.writeFile(file, newContent, "utf-8");
|
|
487
|
+
logger.info('Lang tag configurations written for file "{path}" (file://{file}:{line})', { path: path$12, file, line: lastUpdatedLine });
|
|
475
488
|
return true;
|
|
476
489
|
}
|
|
477
490
|
return false;
|
|
478
491
|
}
|
|
492
|
+
function isConfigSame(c1, c2) {
|
|
493
|
+
if (!c1 && !c2) return true;
|
|
494
|
+
if (c1 && typeof c1 === "object" && c2 && typeof c2 === "object" && JSON5.stringify(c1) === JSON5.stringify(c2)) return true;
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
479
497
|
const CONFIG_FILE_NAME = ".lang-tag.config.js";
|
|
480
498
|
const EXPORTS_FILE_NAME = ".lang-tag.exports.json";
|
|
481
499
|
const LANG_TAG_DEFAULT_CONFIG = {
|
|
@@ -497,7 +515,7 @@ const LANG_TAG_DEFAULT_CONFIG = {
|
|
|
497
515
|
await event.logger.conflict(event.conflict, true);
|
|
498
516
|
},
|
|
499
517
|
onCollectFinish: (event) => {
|
|
500
|
-
event.exit();
|
|
518
|
+
if (event.conflicts.length) event.exit();
|
|
501
519
|
}
|
|
502
520
|
},
|
|
503
521
|
import: {
|
|
@@ -994,7 +1012,6 @@ async function $LT_CMD_RegenerateTags() {
|
|
|
994
1012
|
const path2 = file.substring(charactersToSkip);
|
|
995
1013
|
const localDirty = await checkAndRegenerateFileLangTags(config, logger, file, path2);
|
|
996
1014
|
if (localDirty) {
|
|
997
|
-
logger.info('Lang tag configurations written for file "{path}"', { path: path2 });
|
|
998
1015
|
dirty = true;
|
|
999
1016
|
}
|
|
1000
1017
|
}
|
|
@@ -1201,18 +1218,18 @@ async function $LT_GroupTagsToNamespaces({ logger, files, config }) {
|
|
|
1201
1218
|
}
|
|
1202
1219
|
if (allConflicts.length > 0) {
|
|
1203
1220
|
logger.warn(`Found ${allConflicts.length} conflicts.`);
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
});
|
|
1213
|
-
if (!shouldContinue) {
|
|
1214
|
-
throw new Error(`LangTagConflictResolution:Processing stopped due to collect finish handler`);
|
|
1221
|
+
}
|
|
1222
|
+
if (config.collect?.onCollectFinish) {
|
|
1223
|
+
let shouldContinue = true;
|
|
1224
|
+
config.collect.onCollectFinish({
|
|
1225
|
+
conflicts: allConflicts,
|
|
1226
|
+
logger,
|
|
1227
|
+
exit() {
|
|
1228
|
+
shouldContinue = false;
|
|
1215
1229
|
}
|
|
1230
|
+
});
|
|
1231
|
+
if (!shouldContinue) {
|
|
1232
|
+
throw new Error(`LangTagConflictResolution:Processing stopped due to collect finish handler`);
|
|
1216
1233
|
}
|
|
1217
1234
|
}
|
|
1218
1235
|
return namespaces;
|
|
@@ -1395,10 +1412,7 @@ async function handleFile(config, logger, cwdRelativeFilePath, event) {
|
|
|
1395
1412
|
}
|
|
1396
1413
|
const cwd = process.cwd();
|
|
1397
1414
|
const absoluteFilePath = path.join(cwd, cwdRelativeFilePath);
|
|
1398
|
-
|
|
1399
|
-
if (dirty) {
|
|
1400
|
-
logger.info(`Lang tag configurations written for file "{filePath}"`, { filePath: cwdRelativeFilePath });
|
|
1401
|
-
}
|
|
1415
|
+
await checkAndRegenerateFileLangTags(config, logger, absoluteFilePath, cwdRelativeFilePath);
|
|
1402
1416
|
const files = await $LT_CollectCandidateFilesWithTags({ filesToScan: [cwdRelativeFilePath], config, logger });
|
|
1403
1417
|
const namespaces = await $LT_GroupTagsToNamespaces({ logger, files, config });
|
|
1404
1418
|
const changedNamespaces = await $LT_WriteToNamespaces({ config, namespaces, logger });
|
|
@@ -1428,7 +1442,7 @@ async function detectModuleSystem() {
|
|
|
1428
1442
|
}
|
|
1429
1443
|
async function generateDefaultConfig() {
|
|
1430
1444
|
const moduleSystem = await detectModuleSystem();
|
|
1431
|
-
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator } = require('@lang-tag/cli/algorithms');`;
|
|
1445
|
+
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator, configKeeper } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator, configKeeper } = require('@lang-tag/cli/algorithms');`;
|
|
1432
1446
|
const exportStatement = moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
|
|
1433
1447
|
return `${importStatement}
|
|
1434
1448
|
|
|
@@ -1440,6 +1454,7 @@ const generationAlgorithm = pathBasedConfigGenerator({
|
|
|
1440
1454
|
clearOnDefaultNamespace: true,
|
|
1441
1455
|
ignoreDirectories: ['core', 'utils', 'helpers']
|
|
1442
1456
|
});
|
|
1457
|
+
const keeper = configKeeper();
|
|
1443
1458
|
|
|
1444
1459
|
/** @type {import('@lang-tag/cli/config').LangTagCLIConfig} */
|
|
1445
1460
|
const config = {
|
|
@@ -1452,9 +1467,11 @@ const config = {
|
|
|
1452
1467
|
// We do not modify imported configurations
|
|
1453
1468
|
if (event.isImportedLibrary) return;
|
|
1454
1469
|
|
|
1455
|
-
if (event.config?.
|
|
1470
|
+
if (event.config?.keep === 'both') return;
|
|
1456
1471
|
|
|
1457
1472
|
await generationAlgorithm(event);
|
|
1473
|
+
|
|
1474
|
+
await keeper(event);
|
|
1458
1475
|
},
|
|
1459
1476
|
collect: {
|
|
1460
1477
|
defaultNamespace: 'common',
|
|
@@ -1464,7 +1481,7 @@ const config = {
|
|
|
1464
1481
|
// Call event.exit(); to terminate the process upon the first conflict
|
|
1465
1482
|
},
|
|
1466
1483
|
onCollectFinish: event => {
|
|
1467
|
-
event.exit(); // Stop the process to avoid merging on conflict
|
|
1484
|
+
if (event.conflicts.length) event.exit(); // Stop the process to avoid merging on conflict
|
|
1468
1485
|
}
|
|
1469
1486
|
},
|
|
1470
1487
|
translationArgPosition: 1,
|
package/index.js
CHANGED
|
@@ -153,7 +153,7 @@ class $LT_TagProcessor {
|
|
|
153
153
|
}
|
|
154
154
|
const tag = R.tag;
|
|
155
155
|
let newTranslationsString = R.translations;
|
|
156
|
-
if (!newTranslationsString) newTranslationsString = this.config.translationArgPosition === 1 ? tag.parameter1Text : tag.parameter2Text;
|
|
156
|
+
if (!newTranslationsString) newTranslationsString = this.config.translationArgPosition === 1 ? tag.parameter1Text : tag.parameter2Text || "{}";
|
|
157
157
|
else if (typeof newTranslationsString !== "string") newTranslationsString = JSON5.stringify(newTranslationsString);
|
|
158
158
|
if (!newTranslationsString) throw new Error("Tag must have translations provided!");
|
|
159
159
|
try {
|
|
@@ -171,6 +171,9 @@ class $LT_TagProcessor {
|
|
|
171
171
|
throw new Error(`Tag config is invalid object! Config: ${newConfigString}`);
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
+
if (newConfigString === null && this.config.translationArgPosition === 2) {
|
|
175
|
+
newConfigString = "{}";
|
|
176
|
+
}
|
|
174
177
|
const arg1 = this.config.translationArgPosition === 1 ? newTranslationsString : newConfigString;
|
|
175
178
|
const arg2 = this.config.translationArgPosition === 1 ? newConfigString : newTranslationsString;
|
|
176
179
|
let tagFunction = `${this.config.tagName}(${arg1}`;
|
|
@@ -427,35 +430,50 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
|
427
430
|
return false;
|
|
428
431
|
}
|
|
429
432
|
const replacements = [];
|
|
433
|
+
let lastUpdatedLine = 0;
|
|
430
434
|
for (let tag of tags) {
|
|
431
435
|
let newConfig = void 0;
|
|
432
436
|
let shouldUpdate = false;
|
|
433
437
|
const frozenConfig = tag.parameterConfig ? deepFreezeObject(tag.parameterConfig) : tag.parameterConfig;
|
|
434
|
-
|
|
438
|
+
const event = {
|
|
435
439
|
langTagConfig: config,
|
|
436
440
|
config: frozenConfig,
|
|
437
441
|
absolutePath: file,
|
|
438
442
|
relativePath: path2,
|
|
439
443
|
isImportedLibrary: path2.startsWith(libraryImportsDir),
|
|
440
|
-
|
|
441
|
-
|
|
444
|
+
isSaved: false,
|
|
445
|
+
savedConfig: void 0,
|
|
446
|
+
save: (updatedConfig, triggerName) => {
|
|
447
|
+
if (!updatedConfig && updatedConfig !== null) throw new Error("Wrong config data");
|
|
448
|
+
newConfig = updatedConfig;
|
|
442
449
|
shouldUpdate = true;
|
|
450
|
+
event.isSaved = true;
|
|
451
|
+
event.savedConfig = updatedConfig;
|
|
452
|
+
logger.debug('Called save for "{path}" with config "{config}" triggered by: ("{trigger}")', { path: path2, config: JSON.stringify(updatedConfig), trigger: triggerName || "-" });
|
|
443
453
|
}
|
|
444
|
-
}
|
|
454
|
+
};
|
|
455
|
+
await config.onConfigGeneration(event);
|
|
445
456
|
if (!shouldUpdate) {
|
|
446
457
|
continue;
|
|
447
458
|
}
|
|
448
|
-
|
|
459
|
+
lastUpdatedLine = tag.line;
|
|
460
|
+
if (!isConfigSame(tag.parameterConfig, newConfig)) {
|
|
449
461
|
replacements.push({ tag, config: newConfig });
|
|
450
462
|
}
|
|
451
463
|
}
|
|
452
464
|
if (replacements.length) {
|
|
453
465
|
const newContent = processor.replaceTags(fileContent, replacements);
|
|
454
466
|
await writeFile(file, newContent, "utf-8");
|
|
467
|
+
logger.info('Lang tag configurations written for file "{path}" (file://{file}:{line})', { path: path2, file, line: lastUpdatedLine });
|
|
455
468
|
return true;
|
|
456
469
|
}
|
|
457
470
|
return false;
|
|
458
471
|
}
|
|
472
|
+
function isConfigSame(c1, c2) {
|
|
473
|
+
if (!c1 && !c2) return true;
|
|
474
|
+
if (c1 && typeof c1 === "object" && c2 && typeof c2 === "object" && JSON5.stringify(c1) === JSON5.stringify(c2)) return true;
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
459
477
|
const CONFIG_FILE_NAME = ".lang-tag.config.js";
|
|
460
478
|
const EXPORTS_FILE_NAME = ".lang-tag.exports.json";
|
|
461
479
|
const LANG_TAG_DEFAULT_CONFIG = {
|
|
@@ -477,7 +495,7 @@ const LANG_TAG_DEFAULT_CONFIG = {
|
|
|
477
495
|
await event.logger.conflict(event.conflict, true);
|
|
478
496
|
},
|
|
479
497
|
onCollectFinish: (event) => {
|
|
480
|
-
event.exit();
|
|
498
|
+
if (event.conflicts.length) event.exit();
|
|
481
499
|
}
|
|
482
500
|
},
|
|
483
501
|
import: {
|
|
@@ -974,7 +992,6 @@ async function $LT_CMD_RegenerateTags() {
|
|
|
974
992
|
const path2 = file.substring(charactersToSkip);
|
|
975
993
|
const localDirty = await checkAndRegenerateFileLangTags(config, logger, file, path2);
|
|
976
994
|
if (localDirty) {
|
|
977
|
-
logger.info('Lang tag configurations written for file "{path}"', { path: path2 });
|
|
978
995
|
dirty = true;
|
|
979
996
|
}
|
|
980
997
|
}
|
|
@@ -1181,18 +1198,18 @@ async function $LT_GroupTagsToNamespaces({ logger, files, config }) {
|
|
|
1181
1198
|
}
|
|
1182
1199
|
if (allConflicts.length > 0) {
|
|
1183
1200
|
logger.warn(`Found ${allConflicts.length} conflicts.`);
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
});
|
|
1193
|
-
if (!shouldContinue) {
|
|
1194
|
-
throw new Error(`LangTagConflictResolution:Processing stopped due to collect finish handler`);
|
|
1201
|
+
}
|
|
1202
|
+
if (config.collect?.onCollectFinish) {
|
|
1203
|
+
let shouldContinue = true;
|
|
1204
|
+
config.collect.onCollectFinish({
|
|
1205
|
+
conflicts: allConflicts,
|
|
1206
|
+
logger,
|
|
1207
|
+
exit() {
|
|
1208
|
+
shouldContinue = false;
|
|
1195
1209
|
}
|
|
1210
|
+
});
|
|
1211
|
+
if (!shouldContinue) {
|
|
1212
|
+
throw new Error(`LangTagConflictResolution:Processing stopped due to collect finish handler`);
|
|
1196
1213
|
}
|
|
1197
1214
|
}
|
|
1198
1215
|
return namespaces;
|
|
@@ -1375,10 +1392,7 @@ async function handleFile(config, logger, cwdRelativeFilePath, event) {
|
|
|
1375
1392
|
}
|
|
1376
1393
|
const cwd = process.cwd();
|
|
1377
1394
|
const absoluteFilePath = path__default.join(cwd, cwdRelativeFilePath);
|
|
1378
|
-
|
|
1379
|
-
if (dirty) {
|
|
1380
|
-
logger.info(`Lang tag configurations written for file "{filePath}"`, { filePath: cwdRelativeFilePath });
|
|
1381
|
-
}
|
|
1395
|
+
await checkAndRegenerateFileLangTags(config, logger, absoluteFilePath, cwdRelativeFilePath);
|
|
1382
1396
|
const files = await $LT_CollectCandidateFilesWithTags({ filesToScan: [cwdRelativeFilePath], config, logger });
|
|
1383
1397
|
const namespaces = await $LT_GroupTagsToNamespaces({ logger, files, config });
|
|
1384
1398
|
const changedNamespaces = await $LT_WriteToNamespaces({ config, namespaces, logger });
|
|
@@ -1408,7 +1422,7 @@ async function detectModuleSystem() {
|
|
|
1408
1422
|
}
|
|
1409
1423
|
async function generateDefaultConfig() {
|
|
1410
1424
|
const moduleSystem = await detectModuleSystem();
|
|
1411
|
-
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator } = require('@lang-tag/cli/algorithms');`;
|
|
1425
|
+
const importStatement = moduleSystem === "esm" ? `import { pathBasedConfigGenerator, configKeeper } from '@lang-tag/cli/algorithms';` : `const { pathBasedConfigGenerator, configKeeper } = require('@lang-tag/cli/algorithms');`;
|
|
1412
1426
|
const exportStatement = moduleSystem === "esm" ? "export default config;" : "module.exports = config;";
|
|
1413
1427
|
return `${importStatement}
|
|
1414
1428
|
|
|
@@ -1420,6 +1434,7 @@ const generationAlgorithm = pathBasedConfigGenerator({
|
|
|
1420
1434
|
clearOnDefaultNamespace: true,
|
|
1421
1435
|
ignoreDirectories: ['core', 'utils', 'helpers']
|
|
1422
1436
|
});
|
|
1437
|
+
const keeper = configKeeper();
|
|
1423
1438
|
|
|
1424
1439
|
/** @type {import('@lang-tag/cli/config').LangTagCLIConfig} */
|
|
1425
1440
|
const config = {
|
|
@@ -1432,9 +1447,11 @@ const config = {
|
|
|
1432
1447
|
// We do not modify imported configurations
|
|
1433
1448
|
if (event.isImportedLibrary) return;
|
|
1434
1449
|
|
|
1435
|
-
if (event.config?.
|
|
1450
|
+
if (event.config?.keep === 'both') return;
|
|
1436
1451
|
|
|
1437
1452
|
await generationAlgorithm(event);
|
|
1453
|
+
|
|
1454
|
+
await keeper(event);
|
|
1438
1455
|
},
|
|
1439
1456
|
collect: {
|
|
1440
1457
|
defaultNamespace: 'common',
|
|
@@ -1444,7 +1461,7 @@ const config = {
|
|
|
1444
1461
|
// Call event.exit(); to terminate the process upon the first conflict
|
|
1445
1462
|
},
|
|
1446
1463
|
onCollectFinish: event => {
|
|
1447
|
-
event.exit(); // Stop the process to avoid merging on conflict
|
|
1464
|
+
if (event.conflicts.length) event.exit(); // Stop the process to avoid merging on conflict
|
|
1448
1465
|
}
|
|
1449
1466
|
},
|
|
1450
1467
|
translationArgPosition: 1,
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lang-tag/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/TheTonsOfCode/lang-tag"
|
|
10
|
+
"url": "https://github.com/TheTonsOfCode/lang-tag-cli"
|
|
11
11
|
},
|
|
12
12
|
"author": "TheTonsOfCode",
|
|
13
13
|
"license": "MIT",
|
|
@@ -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, {
|