@lang-tag/cli 0.12.1 → 0.12.2

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.
@@ -10,6 +10,20 @@ export interface ConfigKeeperOptions {
10
10
  * ```
11
11
  */
12
12
  propertyName?: string;
13
+ /**
14
+ * When true, ensures the keep property is always placed at the end of the configuration object.
15
+ * This improves readability by keeping metadata properties separate from core config.
16
+ * @default true
17
+ * @example
18
+ * ```tsx
19
+ * // With keepPropertyAtEnd: true
20
+ * { namespace: 'common', path: 'button', keep: 'namespace' }
21
+ *
22
+ * // With keepPropertyAtEnd: false
23
+ * // Order is not guaranteed
24
+ * ```
25
+ */
26
+ keepPropertyAtEnd?: boolean;
13
27
  }
14
28
  /**
15
29
  * Creates a config keeper algorithm that preserves original configuration values
@@ -24,17 +24,24 @@ export interface PathBasedConfigGeneratorOptions {
24
24
  */
25
25
  ignoreDirectories?: string[];
26
26
  /**
27
- * When true, automatically extracts root directory names from the config.includes patterns
28
- * and adds them to the ignoreDirectories list.
27
+ * When true, automatically removes the root directory from the path if it matches
28
+ * one of the root directories extracted from config.includes patterns.
29
+ * Unlike ignoreDirectories, this only removes the FIRST occurrence if the path starts with it.
29
30
  *
30
31
  * @default false
31
32
  *
32
33
  * @example
33
34
  * // With includes: ['src/**\/*.{js,ts,jsx,tsx}']
34
- * // Automatically ignores: ['src']
35
+ * // Path: 'src/components/Button.tsx' → removes root 'src' → 'components/Button.tsx'
35
36
  *
36
- * // With includes: ['(src|app)/**\/*.{js,ts,jsx,tsx}', 'components/**\/*.{jsx,tsx}']
37
- * // Automatically ignores: ['src', 'app', 'components']
37
+ * // With includes: ['app/**\/*.{js,ts,jsx,tsx}', 'components/**\/*.{jsx,tsx}']
38
+ * // Path: 'app/dashboard/components/utils.tsx' → removes root 'app' 'dashboard/components/utils.tsx'
39
+ * // Note: 'components' in the middle is NOT removed (only root occurrences)
40
+ *
41
+ * // With includes: ['(src|app)/**\/*.{js,ts,jsx,tsx}']
42
+ * // Extracts root directories: ['src', 'app']
43
+ * // Path: 'src/features/auth.tsx' → removes root 'src' → 'features/auth.tsx'
44
+ * // Path: 'app/pages/home.tsx' → removes root 'app' → 'pages/home.tsx'
38
45
  */
39
46
  ignoreIncludesRootDirectories?: boolean;
40
47
  /**
@@ -36,11 +36,6 @@ function pathBasedConfigGenerator(options = {}) {
36
36
  return async (event) => {
37
37
  const { relativePath, langTagConfig } = event;
38
38
  const actualFallbackNamespace = fallbackNamespace ?? langTagConfig.collect?.defaultNamespace;
39
- let finalIgnoreDirectories = [...ignoreDirectories];
40
- if (ignoreIncludesRootDirectories && langTagConfig.includes) {
41
- const extractedDirectories = extractRootDirectoriesFromIncludes(langTagConfig.includes);
42
- finalIgnoreDirectories = [.../* @__PURE__ */ new Set([...finalIgnoreDirectories, ...extractedDirectories])];
43
- }
44
39
  let pathSegments = relativePath.split(path.sep).filter(Boolean);
45
40
  if (pathSegments.length === 0) {
46
41
  return;
@@ -59,8 +54,14 @@ function pathBasedConfigGenerator(options = {}) {
59
54
  }
60
55
  return segment;
61
56
  }).filter((seg) => seg !== null);
57
+ if (ignoreIncludesRootDirectories && langTagConfig.includes && pathSegments.length > 0) {
58
+ const extractedDirectories = extractRootDirectoriesFromIncludes(langTagConfig.includes);
59
+ if (extractedDirectories.includes(pathSegments[0])) {
60
+ pathSegments = pathSegments.slice(1);
61
+ }
62
+ }
62
63
  pathSegments = applyStructuredIgnore(pathSegments, ignoreStructured);
63
- pathSegments = pathSegments.filter((seg) => !finalIgnoreDirectories.includes(seg));
64
+ pathSegments = pathSegments.filter((seg) => !ignoreDirectories.includes(seg));
64
65
  let namespace;
65
66
  let path$1;
66
67
  if (pathSegments.length >= 1) {
@@ -107,6 +108,8 @@ function pathBasedConfigGenerator(options = {}) {
107
108
  }
108
109
  if (path$1) {
109
110
  newConfig.path = path$1;
111
+ } else {
112
+ delete newConfig.path;
110
113
  }
111
114
  }
112
115
  if (Object.keys(newConfig).length > 0) {
@@ -169,6 +172,7 @@ function extractRootDirectoriesFromIncludes(includes) {
169
172
  const TRIGGER_NAME = "config-keeper";
170
173
  function configKeeper(options = {}) {
171
174
  const propertyName = options.propertyName ?? "keep";
175
+ const keepPropertyAtEnd = options.keepPropertyAtEnd ?? true;
172
176
  return async (event) => {
173
177
  if (!event.isSaved) {
174
178
  return;
@@ -191,14 +195,38 @@ function configKeeper(options = {}) {
191
195
  } else {
192
196
  restoredConfig = { ...event.savedConfig };
193
197
  }
194
- if ((keepMode === "namespace" || keepMode === "both") && event.config.namespace !== void 0) {
195
- restoredConfig.namespace = event.config.namespace;
198
+ let needsSave = false;
199
+ const restorePropertyIfNeeded = (propertyKey) => {
200
+ if (!event.config) return;
201
+ const shouldRestore = (keepMode === propertyKey || keepMode === "both") && event.config[propertyKey] !== void 0;
202
+ if (shouldRestore && restoredConfig[propertyKey] !== event.config[propertyKey]) {
203
+ restoredConfig[propertyKey] = event.config[propertyKey];
204
+ needsSave = true;
205
+ }
206
+ };
207
+ restorePropertyIfNeeded("namespace");
208
+ restorePropertyIfNeeded("path");
209
+ const keepPropertyExistedBefore = event.savedConfig && event.savedConfig[propertyName] !== void 0;
210
+ if (!keepPropertyExistedBefore) {
211
+ needsSave = true;
212
+ }
213
+ if (keepPropertyAtEnd && event.savedConfig && !needsSave) {
214
+ const savedKeys = Object.keys(event.savedConfig);
215
+ const keepIndex = savedKeys.indexOf(propertyName);
216
+ const isKeepAtEnd = keepIndex === savedKeys.length - 1;
217
+ if (!isKeepAtEnd && keepIndex !== -1) {
218
+ needsSave = true;
219
+ }
220
+ }
221
+ if (!needsSave) {
222
+ return;
196
223
  }
197
- if ((keepMode === "path" || keepMode === "both") && event.config.path !== void 0) {
198
- restoredConfig.path = event.config.path;
224
+ const finalConfig = { ...restoredConfig };
225
+ if (keepPropertyAtEnd) {
226
+ delete finalConfig[propertyName];
199
227
  }
200
- restoredConfig[propertyName] = keepMode;
201
- event.save(restoredConfig, TRIGGER_NAME);
228
+ finalConfig[propertyName] = keepMode;
229
+ event.save(finalConfig, TRIGGER_NAME);
202
230
  };
203
231
  }
204
232
  exports.configKeeper = configKeeper;
@@ -17,11 +17,6 @@ function pathBasedConfigGenerator(options = {}) {
17
17
  return async (event) => {
18
18
  const { relativePath, langTagConfig } = event;
19
19
  const actualFallbackNamespace = fallbackNamespace ?? langTagConfig.collect?.defaultNamespace;
20
- let finalIgnoreDirectories = [...ignoreDirectories];
21
- if (ignoreIncludesRootDirectories && langTagConfig.includes) {
22
- const extractedDirectories = extractRootDirectoriesFromIncludes(langTagConfig.includes);
23
- finalIgnoreDirectories = [.../* @__PURE__ */ new Set([...finalIgnoreDirectories, ...extractedDirectories])];
24
- }
25
20
  let pathSegments = relativePath.split(sep).filter(Boolean);
26
21
  if (pathSegments.length === 0) {
27
22
  return;
@@ -40,8 +35,14 @@ function pathBasedConfigGenerator(options = {}) {
40
35
  }
41
36
  return segment;
42
37
  }).filter((seg) => seg !== null);
38
+ if (ignoreIncludesRootDirectories && langTagConfig.includes && pathSegments.length > 0) {
39
+ const extractedDirectories = extractRootDirectoriesFromIncludes(langTagConfig.includes);
40
+ if (extractedDirectories.includes(pathSegments[0])) {
41
+ pathSegments = pathSegments.slice(1);
42
+ }
43
+ }
43
44
  pathSegments = applyStructuredIgnore(pathSegments, ignoreStructured);
44
- pathSegments = pathSegments.filter((seg) => !finalIgnoreDirectories.includes(seg));
45
+ pathSegments = pathSegments.filter((seg) => !ignoreDirectories.includes(seg));
45
46
  let namespace;
46
47
  let path;
47
48
  if (pathSegments.length >= 1) {
@@ -88,6 +89,8 @@ function pathBasedConfigGenerator(options = {}) {
88
89
  }
89
90
  if (path) {
90
91
  newConfig.path = path;
92
+ } else {
93
+ delete newConfig.path;
91
94
  }
92
95
  }
93
96
  if (Object.keys(newConfig).length > 0) {
@@ -150,6 +153,7 @@ function extractRootDirectoriesFromIncludes(includes) {
150
153
  const TRIGGER_NAME = "config-keeper";
151
154
  function configKeeper(options = {}) {
152
155
  const propertyName = options.propertyName ?? "keep";
156
+ const keepPropertyAtEnd = options.keepPropertyAtEnd ?? true;
153
157
  return async (event) => {
154
158
  if (!event.isSaved) {
155
159
  return;
@@ -172,14 +176,38 @@ function configKeeper(options = {}) {
172
176
  } else {
173
177
  restoredConfig = { ...event.savedConfig };
174
178
  }
175
- if ((keepMode === "namespace" || keepMode === "both") && event.config.namespace !== void 0) {
176
- restoredConfig.namespace = event.config.namespace;
179
+ let needsSave = false;
180
+ const restorePropertyIfNeeded = (propertyKey) => {
181
+ if (!event.config) return;
182
+ const shouldRestore = (keepMode === propertyKey || keepMode === "both") && event.config[propertyKey] !== void 0;
183
+ if (shouldRestore && restoredConfig[propertyKey] !== event.config[propertyKey]) {
184
+ restoredConfig[propertyKey] = event.config[propertyKey];
185
+ needsSave = true;
186
+ }
187
+ };
188
+ restorePropertyIfNeeded("namespace");
189
+ restorePropertyIfNeeded("path");
190
+ const keepPropertyExistedBefore = event.savedConfig && event.savedConfig[propertyName] !== void 0;
191
+ if (!keepPropertyExistedBefore) {
192
+ needsSave = true;
193
+ }
194
+ if (keepPropertyAtEnd && event.savedConfig && !needsSave) {
195
+ const savedKeys = Object.keys(event.savedConfig);
196
+ const keepIndex = savedKeys.indexOf(propertyName);
197
+ const isKeepAtEnd = keepIndex === savedKeys.length - 1;
198
+ if (!isKeepAtEnd && keepIndex !== -1) {
199
+ needsSave = true;
200
+ }
201
+ }
202
+ if (!needsSave) {
203
+ return;
177
204
  }
178
- if ((keepMode === "path" || keepMode === "both") && event.config.path !== void 0) {
179
- restoredConfig.path = event.config.path;
205
+ const finalConfig = { ...restoredConfig };
206
+ if (keepPropertyAtEnd) {
207
+ delete finalConfig[propertyName];
180
208
  }
181
- restoredConfig[propertyName] = keepMode;
182
- event.save(restoredConfig, TRIGGER_NAME);
209
+ finalConfig[propertyName] = keepMode;
210
+ event.save(finalConfig, TRIGGER_NAME);
183
211
  };
184
212
  }
185
213
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lang-tag/cli",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -10,7 +10,7 @@ import {
10
10
  import { createCallableTranslations } from 'lang-tag';
11
11
  {{/isTypeScript}}
12
12
  {{#isReact}}
13
- import { ReactNode, useMemo } from 'react';
13
+ import React, { ReactNode, useMemo } from 'react';
14
14
  {{/isReact}}
15
15
 
16
16
  {{#isTypeScript}}
@@ -13,7 +13,7 @@ import {
13
13
  import { createCallableTranslations, normalizeTranslations, lookupTranslation } from 'lang-tag';
14
14
  {{/isTypeScript}}
15
15
  {{#isReact}}
16
- import {
16
+ import React, {
17
17
  createContext,
18
18
  useContext,
19
19
  useMemo,
@@ -49,7 +49,12 @@ export function processPlaceholders(
49
49
  return parts.join('');
50
50
  }
51
51
 
52
- return parts{{#isTypeScript}} as unknown as string{{/isTypeScript}};
52
+ return parts.map((part, index) => {
53
+ if (React.isValidElement(part)) {
54
+ return React.cloneElement(part, { key: index });
55
+ }
56
+ return React.createElement(React.Fragment, { key: index }, part);
57
+ }){{#isTypeScript}} as unknown as string{{/isTypeScript}};
53
58
  }
54
59
  {{/isReact}}
55
60