@lang-tag/cli 0.10.0 → 0.10.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 +1 -1
- package/config.d.ts +20 -15
- package/index.cjs +220 -16
- package/index.js +220 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Lang-tag
|
|
1
|
+
# Lang-tag CLI
|
|
2
2
|
|
|
3
3
|
A professional solution for managing translations in modern JavaScript/TypeScript projects, especially those using component-based architectures. `lang-tag` simplifies internationalization by allowing you to define translation keys directly within the components where they are used. Translations become local, callable function objects with full TypeScript support, IntelliSense, and compile-time safety.
|
|
4
4
|
|
package/config.d.ts
CHANGED
|
@@ -98,9 +98,12 @@ export interface LangTagCLIConfig {
|
|
|
98
98
|
* A function called for each found lang tag before processing.
|
|
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
|
+
* Changes made inside this function are **applied only if you explicitly call**
|
|
103
|
+
* `event.save(configuration)`. Returning a value or modifying the event object
|
|
104
|
+
* without calling `save()` will **not** update the configuration.
|
|
102
105
|
*/
|
|
103
|
-
onConfigGeneration: (
|
|
106
|
+
onConfigGeneration: (event: LangTagCLIConfigGenerationEvent) => Promise<void>;
|
|
104
107
|
debug?: boolean;
|
|
105
108
|
}
|
|
106
109
|
/**
|
|
@@ -131,19 +134,6 @@ export interface LangTagCLIOnImportActions {
|
|
|
131
134
|
/** Sets the configuration for the currently imported tag. */
|
|
132
135
|
setConfig: (config: LangTagTranslationsConfig) => void;
|
|
133
136
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Parameters passed to the `onConfigGeneration` configuration function.
|
|
136
|
-
*/
|
|
137
|
-
export interface LangTagCLIOnConfigGenerationParams {
|
|
138
|
-
/** The absolute path to the source file being processed. */
|
|
139
|
-
fullPath: string;
|
|
140
|
-
/** The path of the source file relative to the project root (where the command was invoked). */
|
|
141
|
-
path: string;
|
|
142
|
-
/** True if the file being processed is located within the configured library import directory (`config.import.dir`). */
|
|
143
|
-
isImportedLibrary: boolean;
|
|
144
|
-
/** The configuration object extracted from the lang tag's options argument (e.g., `{ namespace: 'common', path: 'my.path' }`). */
|
|
145
|
-
config: LangTagTranslationsConfig;
|
|
146
|
-
}
|
|
147
137
|
type Validity = 'ok' | 'invalid-param-1' | 'invalid-param-2' | 'translations-not-found';
|
|
148
138
|
export interface LangTagCLIProcessedTag {
|
|
149
139
|
fullMatch: string;
|
|
@@ -171,6 +161,21 @@ export interface LangTagCLIConflict {
|
|
|
171
161
|
tagB: LangTagCLITagConflictInfo;
|
|
172
162
|
conflictType: 'path_overwrite' | 'type_mismatch';
|
|
173
163
|
}
|
|
164
|
+
export interface LangTagCLIConfigGenerationEvent {
|
|
165
|
+
/** The absolute path to the source file being processed. */
|
|
166
|
+
absolutePath: string;
|
|
167
|
+
/** The path of the source file relative to the project root (where the command was invoked). */
|
|
168
|
+
relativePath: string;
|
|
169
|
+
/** True if the file being processed is located within the configured library import directory (`config.import.dir`). */
|
|
170
|
+
isImportedLibrary: boolean;
|
|
171
|
+
/** The configuration object extracted from the lang tag's options argument (e.g., `{ namespace: 'common', path: 'my.path' }`). */
|
|
172
|
+
config: LangTagTranslationsConfig | undefined;
|
|
173
|
+
/**
|
|
174
|
+
* Tells CLI to replace tag configuration
|
|
175
|
+
* undefined = means configuration will be removed
|
|
176
|
+
**/
|
|
177
|
+
save(config: LangTagTranslationsConfig | undefined): void;
|
|
178
|
+
}
|
|
174
179
|
export interface LangTagCLICollectConfigFixEvent {
|
|
175
180
|
config: LangTagTranslationsConfig;
|
|
176
181
|
langTagConfig: LangTagCLIConfig;
|
package/index.cjs
CHANGED
|
@@ -43,6 +43,7 @@ class $LT_TagProcessor {
|
|
|
43
43
|
const optionalVariableAssignment = `(?:\\s*(\\w+)\\s*=\\s*)?`;
|
|
44
44
|
const matches = [];
|
|
45
45
|
let currentIndex = 0;
|
|
46
|
+
const skipRanges = this.buildSkipRanges(fileContent);
|
|
46
47
|
const startPattern = new RegExp(`${optionalVariableAssignment}${tagName}\\(\\s*\\{`, "g");
|
|
47
48
|
while (true) {
|
|
48
49
|
startPattern.lastIndex = currentIndex;
|
|
@@ -50,6 +51,10 @@ class $LT_TagProcessor {
|
|
|
50
51
|
if (!startMatch) break;
|
|
51
52
|
const matchStartIndex = startMatch.index;
|
|
52
53
|
const variableName = startMatch[1] || void 0;
|
|
54
|
+
if (this.isInSkipRange(matchStartIndex, skipRanges)) {
|
|
55
|
+
currentIndex = matchStartIndex + 1;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
53
58
|
let braceCount = 1;
|
|
54
59
|
let i = matchStartIndex + startMatch[0].length;
|
|
55
60
|
while (i < fileContent.length && braceCount > 0) {
|
|
@@ -115,17 +120,29 @@ class $LT_TagProcessor {
|
|
|
115
120
|
let validity = "ok";
|
|
116
121
|
let parameter1 = void 0;
|
|
117
122
|
let parameter2 = void 0;
|
|
123
|
+
if (this.hasTemplateInterpolation(parameter1Text) || parameter2Text && this.hasTemplateInterpolation(parameter2Text)) {
|
|
124
|
+
currentIndex = matchStartIndex + 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
118
127
|
try {
|
|
119
128
|
parameter1 = JSON5.parse(parameter1Text);
|
|
120
129
|
if (parameter2Text) {
|
|
121
130
|
try {
|
|
122
131
|
parameter2 = JSON5.parse(parameter2Text);
|
|
123
132
|
} catch (error) {
|
|
124
|
-
|
|
133
|
+
try {
|
|
134
|
+
parameter2 = JSON5.parse(this.escapeNewlinesInStrings(parameter2Text));
|
|
135
|
+
} catch {
|
|
136
|
+
validity = "invalid-param-2";
|
|
137
|
+
}
|
|
125
138
|
}
|
|
126
139
|
}
|
|
127
140
|
} catch (error) {
|
|
128
|
-
|
|
141
|
+
try {
|
|
142
|
+
parameter1 = JSON5.parse(this.escapeNewlinesInStrings(parameter1Text));
|
|
143
|
+
} catch {
|
|
144
|
+
validity = "invalid-param-1";
|
|
145
|
+
}
|
|
129
146
|
}
|
|
130
147
|
let parameterTranslations = this.config.translationArgPosition === 1 ? parameter1 : parameter2;
|
|
131
148
|
let parameterConfig = this.config.translationArgPosition === 1 ? parameter2 : parameter1;
|
|
@@ -191,6 +208,180 @@ class $LT_TagProcessor {
|
|
|
191
208
|
});
|
|
192
209
|
return fileContent;
|
|
193
210
|
}
|
|
211
|
+
buildSkipRanges(fileContent) {
|
|
212
|
+
const ranges = [];
|
|
213
|
+
let i = 0;
|
|
214
|
+
while (i < fileContent.length) {
|
|
215
|
+
const char = fileContent[i];
|
|
216
|
+
const nextChar = fileContent[i + 1];
|
|
217
|
+
if (char === "/" && nextChar === "/") {
|
|
218
|
+
const start = i;
|
|
219
|
+
i += 2;
|
|
220
|
+
while (i < fileContent.length && fileContent[i] !== "\n") {
|
|
221
|
+
i++;
|
|
222
|
+
}
|
|
223
|
+
ranges.push([start, i]);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (char === "/" && nextChar === "*") {
|
|
227
|
+
const start = i;
|
|
228
|
+
i += 2;
|
|
229
|
+
while (i < fileContent.length - 1) {
|
|
230
|
+
if (fileContent[i] === "*" && fileContent[i + 1] === "/") {
|
|
231
|
+
i += 2;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
i++;
|
|
235
|
+
}
|
|
236
|
+
ranges.push([start, i]);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (char === "'") {
|
|
240
|
+
const start = i;
|
|
241
|
+
i++;
|
|
242
|
+
while (i < fileContent.length) {
|
|
243
|
+
if (fileContent[i] === "\\") {
|
|
244
|
+
i += 2;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (fileContent[i] === "'") {
|
|
248
|
+
i++;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
i++;
|
|
252
|
+
}
|
|
253
|
+
ranges.push([start, i]);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (char === '"') {
|
|
257
|
+
const start = i;
|
|
258
|
+
i++;
|
|
259
|
+
while (i < fileContent.length) {
|
|
260
|
+
if (fileContent[i] === "\\") {
|
|
261
|
+
i += 2;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (fileContent[i] === '"') {
|
|
265
|
+
i++;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
i++;
|
|
269
|
+
}
|
|
270
|
+
ranges.push([start, i]);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (char === "`") {
|
|
274
|
+
const start = i;
|
|
275
|
+
i++;
|
|
276
|
+
while (i < fileContent.length) {
|
|
277
|
+
if (fileContent[i] === "\\") {
|
|
278
|
+
i += 2;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (fileContent[i] === "`") {
|
|
282
|
+
i++;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
i++;
|
|
286
|
+
}
|
|
287
|
+
ranges.push([start, i]);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
i++;
|
|
291
|
+
}
|
|
292
|
+
return ranges;
|
|
293
|
+
}
|
|
294
|
+
isInSkipRange(position, skipRanges) {
|
|
295
|
+
for (const [start, end] of skipRanges) {
|
|
296
|
+
if (position >= start && position < end) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
hasTemplateInterpolation(text) {
|
|
303
|
+
let i = 0;
|
|
304
|
+
while (i < text.length) {
|
|
305
|
+
if (text[i] === "`") {
|
|
306
|
+
i++;
|
|
307
|
+
while (i < text.length) {
|
|
308
|
+
if (text[i] === "\\") {
|
|
309
|
+
i += 2;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (text[i] === "$" && text[i + 1] === "{") {
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
if (text[i] === "`") {
|
|
316
|
+
i++;
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
i++;
|
|
320
|
+
}
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
i++;
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
escapeNewlinesInStrings(text) {
|
|
328
|
+
let result = "";
|
|
329
|
+
let i = 0;
|
|
330
|
+
while (i < text.length) {
|
|
331
|
+
const char = text[i];
|
|
332
|
+
if (char === '"') {
|
|
333
|
+
result += char;
|
|
334
|
+
i++;
|
|
335
|
+
while (i < text.length) {
|
|
336
|
+
if (text[i] === "\\") {
|
|
337
|
+
result += text[i] + text[i + 1];
|
|
338
|
+
i += 2;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (text[i] === "\n") {
|
|
342
|
+
result += "\\n";
|
|
343
|
+
i++;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (text[i] === '"') {
|
|
347
|
+
result += text[i];
|
|
348
|
+
i++;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
result += text[i];
|
|
352
|
+
i++;
|
|
353
|
+
}
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (char === "'") {
|
|
357
|
+
result += char;
|
|
358
|
+
i++;
|
|
359
|
+
while (i < text.length) {
|
|
360
|
+
if (text[i] === "\\") {
|
|
361
|
+
result += text[i] + text[i + 1];
|
|
362
|
+
i += 2;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (text[i] === "\n") {
|
|
366
|
+
result += "\\n";
|
|
367
|
+
i++;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (text[i] === "'") {
|
|
371
|
+
result += text[i];
|
|
372
|
+
i++;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
result += text[i];
|
|
376
|
+
i++;
|
|
377
|
+
}
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
result += char;
|
|
381
|
+
i++;
|
|
382
|
+
}
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
194
385
|
}
|
|
195
386
|
function getLineAndColumn(text, matchIndex) {
|
|
196
387
|
const lines = text.slice(0, matchIndex).split("\n");
|
|
@@ -247,13 +438,19 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path$12) {
|
|
|
247
438
|
}
|
|
248
439
|
const replacements = [];
|
|
249
440
|
for (let tag of tags) {
|
|
250
|
-
|
|
441
|
+
let newConfig = void 0;
|
|
442
|
+
let shouldUpdate = false;
|
|
443
|
+
await config.onConfigGeneration({
|
|
251
444
|
config: tag.parameterConfig,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
isImportedLibrary: path$12.startsWith(libraryImportsDir)
|
|
445
|
+
absolutePath: file,
|
|
446
|
+
relativePath: path$12,
|
|
447
|
+
isImportedLibrary: path$12.startsWith(libraryImportsDir),
|
|
448
|
+
save: (updatedConfig) => {
|
|
449
|
+
newConfig = updatedConfig;
|
|
450
|
+
shouldUpdate = true;
|
|
451
|
+
}
|
|
255
452
|
});
|
|
256
|
-
if (
|
|
453
|
+
if (!shouldUpdate) {
|
|
257
454
|
continue;
|
|
258
455
|
}
|
|
259
456
|
if (JSON5.stringify(tag.parameterConfig) !== JSON5.stringify(newConfig)) {
|
|
@@ -304,7 +501,8 @@ const LANG_TAG_DEFAULT_CONFIG = {
|
|
|
304
501
|
isLibrary: false,
|
|
305
502
|
language: "en",
|
|
306
503
|
translationArgPosition: 1,
|
|
307
|
-
onConfigGeneration: (
|
|
504
|
+
onConfigGeneration: async (event) => {
|
|
505
|
+
}
|
|
308
506
|
};
|
|
309
507
|
async function $LT_ReadConfig(projectPath) {
|
|
310
508
|
const configPath = path$1.resolve(projectPath, CONFIG_FILE_NAME);
|
|
@@ -1099,6 +1297,11 @@ async function $LT_CMD_Collect(options) {
|
|
|
1099
1297
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1100
1298
|
logger.info("Collecting translations from source files...");
|
|
1101
1299
|
const files = await $LT_CollectCandidateFilesWithTags({ config, logger });
|
|
1300
|
+
if (config.debug) {
|
|
1301
|
+
for (let file of files) {
|
|
1302
|
+
logger.debug("Found {count} translations tags inside: {file}", { count: file.tags.length, file: file.relativeFilePath });
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1102
1305
|
if (config.isLibrary) {
|
|
1103
1306
|
await $LT_WriteAsExportFile({ config, logger, files });
|
|
1104
1307
|
return;
|
|
@@ -1224,16 +1427,17 @@ const config = {
|
|
|
1224
1427
|
includes: ['src/**/*.{js,ts,jsx,tsx}'],
|
|
1225
1428
|
excludes: ['node_modules', 'dist', 'build', '**/*.test.ts'],
|
|
1226
1429
|
outputDir: 'public/locales/en',
|
|
1227
|
-
onConfigGeneration:
|
|
1430
|
+
onConfigGeneration: async event => {
|
|
1228
1431
|
// We do not modify imported configurations
|
|
1229
|
-
if (
|
|
1230
|
-
|
|
1231
|
-
//if (!params.config.path) {
|
|
1232
|
-
// params.config.path = 'test';
|
|
1233
|
-
// params.config.namespace = 'testNamespace';
|
|
1234
|
-
//}
|
|
1432
|
+
if (event.isImportedLibrary) return;
|
|
1235
1433
|
|
|
1236
|
-
|
|
1434
|
+
// const config = event.config || {};
|
|
1435
|
+
//
|
|
1436
|
+
// if (!config.path) {
|
|
1437
|
+
// config.path = 'test';
|
|
1438
|
+
// config.namespace = 'testNamespace';
|
|
1439
|
+
// event.save(config);
|
|
1440
|
+
// }
|
|
1237
1441
|
},
|
|
1238
1442
|
collect: {
|
|
1239
1443
|
defaultNamespace: 'common',
|
package/index.js
CHANGED
|
@@ -23,6 +23,7 @@ class $LT_TagProcessor {
|
|
|
23
23
|
const optionalVariableAssignment = `(?:\\s*(\\w+)\\s*=\\s*)?`;
|
|
24
24
|
const matches = [];
|
|
25
25
|
let currentIndex = 0;
|
|
26
|
+
const skipRanges = this.buildSkipRanges(fileContent);
|
|
26
27
|
const startPattern = new RegExp(`${optionalVariableAssignment}${tagName}\\(\\s*\\{`, "g");
|
|
27
28
|
while (true) {
|
|
28
29
|
startPattern.lastIndex = currentIndex;
|
|
@@ -30,6 +31,10 @@ class $LT_TagProcessor {
|
|
|
30
31
|
if (!startMatch) break;
|
|
31
32
|
const matchStartIndex = startMatch.index;
|
|
32
33
|
const variableName = startMatch[1] || void 0;
|
|
34
|
+
if (this.isInSkipRange(matchStartIndex, skipRanges)) {
|
|
35
|
+
currentIndex = matchStartIndex + 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
33
38
|
let braceCount = 1;
|
|
34
39
|
let i = matchStartIndex + startMatch[0].length;
|
|
35
40
|
while (i < fileContent.length && braceCount > 0) {
|
|
@@ -95,17 +100,29 @@ class $LT_TagProcessor {
|
|
|
95
100
|
let validity = "ok";
|
|
96
101
|
let parameter1 = void 0;
|
|
97
102
|
let parameter2 = void 0;
|
|
103
|
+
if (this.hasTemplateInterpolation(parameter1Text) || parameter2Text && this.hasTemplateInterpolation(parameter2Text)) {
|
|
104
|
+
currentIndex = matchStartIndex + 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
98
107
|
try {
|
|
99
108
|
parameter1 = JSON5.parse(parameter1Text);
|
|
100
109
|
if (parameter2Text) {
|
|
101
110
|
try {
|
|
102
111
|
parameter2 = JSON5.parse(parameter2Text);
|
|
103
112
|
} catch (error) {
|
|
104
|
-
|
|
113
|
+
try {
|
|
114
|
+
parameter2 = JSON5.parse(this.escapeNewlinesInStrings(parameter2Text));
|
|
115
|
+
} catch {
|
|
116
|
+
validity = "invalid-param-2";
|
|
117
|
+
}
|
|
105
118
|
}
|
|
106
119
|
}
|
|
107
120
|
} catch (error) {
|
|
108
|
-
|
|
121
|
+
try {
|
|
122
|
+
parameter1 = JSON5.parse(this.escapeNewlinesInStrings(parameter1Text));
|
|
123
|
+
} catch {
|
|
124
|
+
validity = "invalid-param-1";
|
|
125
|
+
}
|
|
109
126
|
}
|
|
110
127
|
let parameterTranslations = this.config.translationArgPosition === 1 ? parameter1 : parameter2;
|
|
111
128
|
let parameterConfig = this.config.translationArgPosition === 1 ? parameter2 : parameter1;
|
|
@@ -171,6 +188,180 @@ class $LT_TagProcessor {
|
|
|
171
188
|
});
|
|
172
189
|
return fileContent;
|
|
173
190
|
}
|
|
191
|
+
buildSkipRanges(fileContent) {
|
|
192
|
+
const ranges = [];
|
|
193
|
+
let i = 0;
|
|
194
|
+
while (i < fileContent.length) {
|
|
195
|
+
const char = fileContent[i];
|
|
196
|
+
const nextChar = fileContent[i + 1];
|
|
197
|
+
if (char === "/" && nextChar === "/") {
|
|
198
|
+
const start = i;
|
|
199
|
+
i += 2;
|
|
200
|
+
while (i < fileContent.length && fileContent[i] !== "\n") {
|
|
201
|
+
i++;
|
|
202
|
+
}
|
|
203
|
+
ranges.push([start, i]);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (char === "/" && nextChar === "*") {
|
|
207
|
+
const start = i;
|
|
208
|
+
i += 2;
|
|
209
|
+
while (i < fileContent.length - 1) {
|
|
210
|
+
if (fileContent[i] === "*" && fileContent[i + 1] === "/") {
|
|
211
|
+
i += 2;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
i++;
|
|
215
|
+
}
|
|
216
|
+
ranges.push([start, i]);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (char === "'") {
|
|
220
|
+
const start = i;
|
|
221
|
+
i++;
|
|
222
|
+
while (i < fileContent.length) {
|
|
223
|
+
if (fileContent[i] === "\\") {
|
|
224
|
+
i += 2;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (fileContent[i] === "'") {
|
|
228
|
+
i++;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
i++;
|
|
232
|
+
}
|
|
233
|
+
ranges.push([start, i]);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (char === '"') {
|
|
237
|
+
const start = i;
|
|
238
|
+
i++;
|
|
239
|
+
while (i < fileContent.length) {
|
|
240
|
+
if (fileContent[i] === "\\") {
|
|
241
|
+
i += 2;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (fileContent[i] === '"') {
|
|
245
|
+
i++;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
i++;
|
|
249
|
+
}
|
|
250
|
+
ranges.push([start, i]);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (char === "`") {
|
|
254
|
+
const start = i;
|
|
255
|
+
i++;
|
|
256
|
+
while (i < fileContent.length) {
|
|
257
|
+
if (fileContent[i] === "\\") {
|
|
258
|
+
i += 2;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (fileContent[i] === "`") {
|
|
262
|
+
i++;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
i++;
|
|
266
|
+
}
|
|
267
|
+
ranges.push([start, i]);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
i++;
|
|
271
|
+
}
|
|
272
|
+
return ranges;
|
|
273
|
+
}
|
|
274
|
+
isInSkipRange(position, skipRanges) {
|
|
275
|
+
for (const [start, end] of skipRanges) {
|
|
276
|
+
if (position >= start && position < end) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
hasTemplateInterpolation(text) {
|
|
283
|
+
let i = 0;
|
|
284
|
+
while (i < text.length) {
|
|
285
|
+
if (text[i] === "`") {
|
|
286
|
+
i++;
|
|
287
|
+
while (i < text.length) {
|
|
288
|
+
if (text[i] === "\\") {
|
|
289
|
+
i += 2;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (text[i] === "$" && text[i + 1] === "{") {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
if (text[i] === "`") {
|
|
296
|
+
i++;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
i++;
|
|
300
|
+
}
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
i++;
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
escapeNewlinesInStrings(text) {
|
|
308
|
+
let result = "";
|
|
309
|
+
let i = 0;
|
|
310
|
+
while (i < text.length) {
|
|
311
|
+
const char = text[i];
|
|
312
|
+
if (char === '"') {
|
|
313
|
+
result += char;
|
|
314
|
+
i++;
|
|
315
|
+
while (i < text.length) {
|
|
316
|
+
if (text[i] === "\\") {
|
|
317
|
+
result += text[i] + text[i + 1];
|
|
318
|
+
i += 2;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (text[i] === "\n") {
|
|
322
|
+
result += "\\n";
|
|
323
|
+
i++;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (text[i] === '"') {
|
|
327
|
+
result += text[i];
|
|
328
|
+
i++;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
result += text[i];
|
|
332
|
+
i++;
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (char === "'") {
|
|
337
|
+
result += char;
|
|
338
|
+
i++;
|
|
339
|
+
while (i < text.length) {
|
|
340
|
+
if (text[i] === "\\") {
|
|
341
|
+
result += text[i] + text[i + 1];
|
|
342
|
+
i += 2;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (text[i] === "\n") {
|
|
346
|
+
result += "\\n";
|
|
347
|
+
i++;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (text[i] === "'") {
|
|
351
|
+
result += text[i];
|
|
352
|
+
i++;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
result += text[i];
|
|
356
|
+
i++;
|
|
357
|
+
}
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
result += char;
|
|
361
|
+
i++;
|
|
362
|
+
}
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
174
365
|
}
|
|
175
366
|
function getLineAndColumn(text, matchIndex) {
|
|
176
367
|
const lines = text.slice(0, matchIndex).split("\n");
|
|
@@ -227,13 +418,19 @@ async function checkAndRegenerateFileLangTags(config, logger, file, path2) {
|
|
|
227
418
|
}
|
|
228
419
|
const replacements = [];
|
|
229
420
|
for (let tag of tags) {
|
|
230
|
-
|
|
421
|
+
let newConfig = void 0;
|
|
422
|
+
let shouldUpdate = false;
|
|
423
|
+
await config.onConfigGeneration({
|
|
231
424
|
config: tag.parameterConfig,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
isImportedLibrary: path2.startsWith(libraryImportsDir)
|
|
425
|
+
absolutePath: file,
|
|
426
|
+
relativePath: path2,
|
|
427
|
+
isImportedLibrary: path2.startsWith(libraryImportsDir),
|
|
428
|
+
save: (updatedConfig) => {
|
|
429
|
+
newConfig = updatedConfig;
|
|
430
|
+
shouldUpdate = true;
|
|
431
|
+
}
|
|
235
432
|
});
|
|
236
|
-
if (
|
|
433
|
+
if (!shouldUpdate) {
|
|
237
434
|
continue;
|
|
238
435
|
}
|
|
239
436
|
if (JSON5.stringify(tag.parameterConfig) !== JSON5.stringify(newConfig)) {
|
|
@@ -284,7 +481,8 @@ const LANG_TAG_DEFAULT_CONFIG = {
|
|
|
284
481
|
isLibrary: false,
|
|
285
482
|
language: "en",
|
|
286
483
|
translationArgPosition: 1,
|
|
287
|
-
onConfigGeneration: (
|
|
484
|
+
onConfigGeneration: async (event) => {
|
|
485
|
+
}
|
|
288
486
|
};
|
|
289
487
|
async function $LT_ReadConfig(projectPath) {
|
|
290
488
|
const configPath = resolve(projectPath, CONFIG_FILE_NAME);
|
|
@@ -1079,6 +1277,11 @@ async function $LT_CMD_Collect(options) {
|
|
|
1079
1277
|
const { config, logger } = await $LT_GetCommandEssentials();
|
|
1080
1278
|
logger.info("Collecting translations from source files...");
|
|
1081
1279
|
const files = await $LT_CollectCandidateFilesWithTags({ config, logger });
|
|
1280
|
+
if (config.debug) {
|
|
1281
|
+
for (let file of files) {
|
|
1282
|
+
logger.debug("Found {count} translations tags inside: {file}", { count: file.tags.length, file: file.relativeFilePath });
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1082
1285
|
if (config.isLibrary) {
|
|
1083
1286
|
await $LT_WriteAsExportFile({ config, logger, files });
|
|
1084
1287
|
return;
|
|
@@ -1204,16 +1407,17 @@ const config = {
|
|
|
1204
1407
|
includes: ['src/**/*.{js,ts,jsx,tsx}'],
|
|
1205
1408
|
excludes: ['node_modules', 'dist', 'build', '**/*.test.ts'],
|
|
1206
1409
|
outputDir: 'public/locales/en',
|
|
1207
|
-
onConfigGeneration:
|
|
1410
|
+
onConfigGeneration: async event => {
|
|
1208
1411
|
// We do not modify imported configurations
|
|
1209
|
-
if (
|
|
1210
|
-
|
|
1211
|
-
//if (!params.config.path) {
|
|
1212
|
-
// params.config.path = 'test';
|
|
1213
|
-
// params.config.namespace = 'testNamespace';
|
|
1214
|
-
//}
|
|
1412
|
+
if (event.isImportedLibrary) return;
|
|
1215
1413
|
|
|
1216
|
-
|
|
1414
|
+
// const config = event.config || {};
|
|
1415
|
+
//
|
|
1416
|
+
// if (!config.path) {
|
|
1417
|
+
// config.path = 'test';
|
|
1418
|
+
// config.namespace = 'testNamespace';
|
|
1419
|
+
// event.save(config);
|
|
1420
|
+
// }
|
|
1217
1421
|
},
|
|
1218
1422
|
collect: {
|
|
1219
1423
|
defaultNamespace: 'common',
|