@lang-tag/cli 0.13.1 → 0.14.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.
@@ -67,6 +67,12 @@ export interface PathBasedConfigGeneratorOptions {
67
67
  * Supports ignoring and renaming segments with special keys:
68
68
  * - `_`: when `false`, ignores the current segment but continues hierarchy
69
69
  * - `>`: renames the current segment to the specified value
70
+ * - `>>`: namespace redirect - jumps to a different namespace and continues processing remaining path segments
71
+ * - String: `>>: 'namespace'` - redirects to specified namespace, remaining segments become path
72
+ * - Object: `>>: { namespace: 'name', pathPrefix: 'prefix.' }` - redirects with optional path prefix
73
+ * - Empty: `>>: ''` or `>>: null/undefined` - uses current segment as namespace
74
+ * - Missing namespace: `>>: { pathPrefix: 'prefix.' }` - uses current segment as namespace with prefix
75
+ * - Nested: deepest `>>` in hierarchy takes precedence
70
76
  * - Regular keys: nested rules or boolean/string for child segments
71
77
  *
72
78
  * @example
@@ -79,6 +85,25 @@ export interface PathBasedConfigGeneratorOptions {
79
85
  * admin: {
80
86
  * '>': 'management', // rename "admin" to "management"
81
87
  * users: true // keep "users" as is (does nothing)
88
+ * },
89
+ * layout: {
90
+ * '>>': 'dashboard' // redirect: everything below layout jumps to "dashboard" namespace
91
+ * },
92
+ * components: {
93
+ * '>>': { // redirect: jump to "ui" namespace with "components." path prefix
94
+ * namespace: 'ui',
95
+ * pathPrefix: 'components'
96
+ * }
97
+ * },
98
+ * features: {
99
+ * '>>': { // redirect: use current segment as namespace with "feature." prefix
100
+ * pathPrefix: 'feature.'
101
+ * }
102
+ * },
103
+ * auth: {
104
+ * '>>': '', // redirect: use current segment as namespace
105
+ * '>>': null, // same as empty string
106
+ * '>>': undefined // same as empty string
82
107
  * }
83
108
  * }
84
109
  * }
@@ -80,6 +80,8 @@ function pathBasedConfigGenerator(options = {}) {
80
80
  namespace = pathSegments[0];
81
81
  if (pathSegments.length > 1) {
82
82
  path$1 = pathSegments.slice(1).join(".");
83
+ } else {
84
+ path$1 = "";
83
85
  }
84
86
  } else {
85
87
  namespace = actualFallbackNamespace;
@@ -163,11 +165,69 @@ function applyStructuredIgnore(segments, structure) {
163
165
  }
164
166
  return result;
165
167
  }
168
+ function addPathPrefixAndSegments(result, pathPrefix, remainingSegments) {
169
+ if (pathPrefix && remainingSegments.length > 0) {
170
+ const cleanPrefix = pathPrefix.endsWith(".") ? pathPrefix.slice(0, -1) : pathPrefix;
171
+ result.push(cleanPrefix, ...remainingSegments);
172
+ } else if (pathPrefix && remainingSegments.length === 0) {
173
+ const cleanPrefix = pathPrefix.endsWith(".") ? pathPrefix.slice(0, -1) : pathPrefix;
174
+ result.push(cleanPrefix);
175
+ } else if (remainingSegments.length > 0) {
176
+ result.push(...remainingSegments);
177
+ }
178
+ }
179
+ function processNamespaceRedirect(redirectRule, remainingSegments, options) {
180
+ const result = [];
181
+ if (redirectRule === null || redirectRule === void 0) {
182
+ if (options?.currentSegment !== void 0) {
183
+ if (!options.ignoreSelf) {
184
+ result.push(options.renameTo || options.currentSegment);
185
+ }
186
+ }
187
+ result.push(...remainingSegments);
188
+ } else if (typeof redirectRule === "string") {
189
+ if (redirectRule === "") {
190
+ if (options?.currentSegment !== void 0) {
191
+ if (!options.ignoreSelf) {
192
+ result.push(options.renameTo || options.currentSegment);
193
+ }
194
+ }
195
+ result.push(...remainingSegments);
196
+ } else {
197
+ result.push(redirectRule);
198
+ result.push(...remainingSegments);
199
+ }
200
+ } else if (typeof redirectRule === "object" && redirectRule !== null) {
201
+ const namespace = redirectRule.namespace;
202
+ const pathPrefix = redirectRule.pathPrefix || "";
203
+ if (namespace === void 0 || namespace === null || namespace === "") {
204
+ if (options?.currentSegment !== void 0) {
205
+ if (!options.ignoreSelf) {
206
+ result.push(options.renameTo || options.currentSegment);
207
+ }
208
+ }
209
+ addPathPrefixAndSegments(result, pathPrefix, remainingSegments);
210
+ } else {
211
+ result.push(namespace);
212
+ addPathPrefixAndSegments(result, pathPrefix, remainingSegments);
213
+ }
214
+ }
215
+ return result;
216
+ }
166
217
  function applyPathRules(segments, structure) {
167
218
  const result = [];
168
219
  let currentStructure = structure;
220
+ let deepestRedirect = null;
169
221
  for (let i = 0; i < segments.length; i++) {
170
222
  const segment = segments[i];
223
+ if (">>" in currentStructure && (!deepestRedirect || !deepestRedirect.context)) {
224
+ const redirectRule = currentStructure[">>"];
225
+ const remainingSegments = segments.slice(i);
226
+ deepestRedirect = {
227
+ rule: redirectRule,
228
+ remainingSegments
229
+ };
230
+ }
171
231
  if (segment in currentStructure) {
172
232
  const rule = currentStructure[segment];
173
233
  if (rule === true) {
@@ -190,6 +250,22 @@ function applyPathRules(segments, structure) {
190
250
  } else if (typeof rule === "object" && rule !== null) {
191
251
  const ignoreSelf = rule["_"] === false;
192
252
  const renameTo = rule[">"];
253
+ const redirectRule = rule[">>"];
254
+ if (">>" in rule) {
255
+ const remainingSegments = segments.slice(i + 1);
256
+ const ruleWithoutRedirect = { ...rule };
257
+ delete ruleWithoutRedirect[">>"];
258
+ const processedRemaining = applyPathRules(remainingSegments, ruleWithoutRedirect);
259
+ deepestRedirect = {
260
+ rule: redirectRule,
261
+ remainingSegments: processedRemaining,
262
+ context: {
263
+ currentSegment: segment,
264
+ renameTo,
265
+ ignoreSelf
266
+ }
267
+ };
268
+ }
193
269
  if (!ignoreSelf) {
194
270
  if (typeof renameTo === "string") {
195
271
  result.push(renameTo);
@@ -204,6 +280,13 @@ function applyPathRules(segments, structure) {
204
280
  result.push(segment);
205
281
  currentStructure = structure;
206
282
  }
283
+ if (deepestRedirect) {
284
+ return processNamespaceRedirect(
285
+ deepestRedirect.rule,
286
+ deepestRedirect.remainingSegments,
287
+ deepestRedirect.context
288
+ );
289
+ }
207
290
  return result;
208
291
  }
209
292
  function applyCaseTransform(str, caseType) {
@@ -61,6 +61,8 @@ function pathBasedConfigGenerator(options = {}) {
61
61
  namespace = pathSegments[0];
62
62
  if (pathSegments.length > 1) {
63
63
  path = pathSegments.slice(1).join(".");
64
+ } else {
65
+ path = "";
64
66
  }
65
67
  } else {
66
68
  namespace = actualFallbackNamespace;
@@ -144,11 +146,69 @@ function applyStructuredIgnore(segments, structure) {
144
146
  }
145
147
  return result;
146
148
  }
149
+ function addPathPrefixAndSegments(result, pathPrefix, remainingSegments) {
150
+ if (pathPrefix && remainingSegments.length > 0) {
151
+ const cleanPrefix = pathPrefix.endsWith(".") ? pathPrefix.slice(0, -1) : pathPrefix;
152
+ result.push(cleanPrefix, ...remainingSegments);
153
+ } else if (pathPrefix && remainingSegments.length === 0) {
154
+ const cleanPrefix = pathPrefix.endsWith(".") ? pathPrefix.slice(0, -1) : pathPrefix;
155
+ result.push(cleanPrefix);
156
+ } else if (remainingSegments.length > 0) {
157
+ result.push(...remainingSegments);
158
+ }
159
+ }
160
+ function processNamespaceRedirect(redirectRule, remainingSegments, options) {
161
+ const result = [];
162
+ if (redirectRule === null || redirectRule === void 0) {
163
+ if (options?.currentSegment !== void 0) {
164
+ if (!options.ignoreSelf) {
165
+ result.push(options.renameTo || options.currentSegment);
166
+ }
167
+ }
168
+ result.push(...remainingSegments);
169
+ } else if (typeof redirectRule === "string") {
170
+ if (redirectRule === "") {
171
+ if (options?.currentSegment !== void 0) {
172
+ if (!options.ignoreSelf) {
173
+ result.push(options.renameTo || options.currentSegment);
174
+ }
175
+ }
176
+ result.push(...remainingSegments);
177
+ } else {
178
+ result.push(redirectRule);
179
+ result.push(...remainingSegments);
180
+ }
181
+ } else if (typeof redirectRule === "object" && redirectRule !== null) {
182
+ const namespace = redirectRule.namespace;
183
+ const pathPrefix = redirectRule.pathPrefix || "";
184
+ if (namespace === void 0 || namespace === null || namespace === "") {
185
+ if (options?.currentSegment !== void 0) {
186
+ if (!options.ignoreSelf) {
187
+ result.push(options.renameTo || options.currentSegment);
188
+ }
189
+ }
190
+ addPathPrefixAndSegments(result, pathPrefix, remainingSegments);
191
+ } else {
192
+ result.push(namespace);
193
+ addPathPrefixAndSegments(result, pathPrefix, remainingSegments);
194
+ }
195
+ }
196
+ return result;
197
+ }
147
198
  function applyPathRules(segments, structure) {
148
199
  const result = [];
149
200
  let currentStructure = structure;
201
+ let deepestRedirect = null;
150
202
  for (let i = 0; i < segments.length; i++) {
151
203
  const segment = segments[i];
204
+ if (">>" in currentStructure && (!deepestRedirect || !deepestRedirect.context)) {
205
+ const redirectRule = currentStructure[">>"];
206
+ const remainingSegments = segments.slice(i);
207
+ deepestRedirect = {
208
+ rule: redirectRule,
209
+ remainingSegments
210
+ };
211
+ }
152
212
  if (segment in currentStructure) {
153
213
  const rule = currentStructure[segment];
154
214
  if (rule === true) {
@@ -171,6 +231,22 @@ function applyPathRules(segments, structure) {
171
231
  } else if (typeof rule === "object" && rule !== null) {
172
232
  const ignoreSelf = rule["_"] === false;
173
233
  const renameTo = rule[">"];
234
+ const redirectRule = rule[">>"];
235
+ if (">>" in rule) {
236
+ const remainingSegments = segments.slice(i + 1);
237
+ const ruleWithoutRedirect = { ...rule };
238
+ delete ruleWithoutRedirect[">>"];
239
+ const processedRemaining = applyPathRules(remainingSegments, ruleWithoutRedirect);
240
+ deepestRedirect = {
241
+ rule: redirectRule,
242
+ remainingSegments: processedRemaining,
243
+ context: {
244
+ currentSegment: segment,
245
+ renameTo,
246
+ ignoreSelf
247
+ }
248
+ };
249
+ }
174
250
  if (!ignoreSelf) {
175
251
  if (typeof renameTo === "string") {
176
252
  result.push(renameTo);
@@ -185,6 +261,13 @@ function applyPathRules(segments, structure) {
185
261
  result.push(segment);
186
262
  currentStructure = structure;
187
263
  }
264
+ if (deepestRedirect) {
265
+ return processNamespaceRedirect(
266
+ deepestRedirect.rule,
267
+ deepestRedirect.remainingSegments,
268
+ deepestRedirect.context
269
+ );
270
+ }
188
271
  return result;
189
272
  }
190
273
  function applyCaseTransform(str, caseType) {
package/index.cjs CHANGED
@@ -80,31 +80,33 @@ class $LT_TagProcessor {
80
80
  while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
81
81
  i++;
82
82
  }
83
- if (i >= fileContent.length || fileContent[i] !== "{") {
84
- currentIndex = matchStartIndex + 1;
85
- continue;
86
- }
87
- braceCount = 1;
88
- const secondParamStart = i;
89
- i++;
90
- while (i < fileContent.length && braceCount > 0) {
91
- if (fileContent[i] === "{") braceCount++;
92
- if (fileContent[i] === "}") braceCount--;
93
- i++;
94
- }
95
- if (braceCount !== 0) {
96
- currentIndex = matchStartIndex + 1;
97
- continue;
98
- }
99
- parameter2Text = fileContent.substring(secondParamStart, i);
100
- while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
101
- i++;
102
- }
103
- if (i < fileContent.length && fileContent[i] === ",") {
83
+ if (i >= fileContent.length || fileContent[i] === ")") ;
84
+ else if (fileContent[i] === "{") {
85
+ braceCount = 1;
86
+ const secondParamStart = i;
104
87
  i++;
88
+ while (i < fileContent.length && braceCount > 0) {
89
+ if (fileContent[i] === "{") braceCount++;
90
+ if (fileContent[i] === "}") braceCount--;
91
+ i++;
92
+ }
93
+ if (braceCount !== 0) {
94
+ currentIndex = matchStartIndex + 1;
95
+ continue;
96
+ }
97
+ parameter2Text = fileContent.substring(secondParamStart, i);
105
98
  while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
106
99
  i++;
107
100
  }
101
+ if (i < fileContent.length && fileContent[i] === ",") {
102
+ i++;
103
+ while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
104
+ i++;
105
+ }
106
+ }
107
+ } else {
108
+ currentIndex = matchStartIndex + 1;
109
+ continue;
108
110
  }
109
111
  } else if (fileContent[i] !== ")") {
110
112
  currentIndex = matchStartIndex + 1;
@@ -547,8 +549,8 @@ async function $LT_ReadConfig(projectPath) {
547
549
  }
548
550
  const userConfig = configModule.default || {};
549
551
  const tn = (userConfig.tagName || "").toLowerCase().replace(/[-_\s]/g, "");
550
- if (tn.includes("langtag")) {
551
- throw new Error('Custom tagName cannot include "langtag"! (It is not recommended for use with libraries)\n');
552
+ if (tn === "langtag" || tn === "lang-tag") {
553
+ throw new Error('Custom tagName cannot be "lang-tag" or "langtag"! (It is not recommended for use with libraries)\n');
552
554
  }
553
555
  return {
554
556
  ...LANG_TAG_DEFAULT_CONFIG,
package/index.js CHANGED
@@ -60,31 +60,33 @@ class $LT_TagProcessor {
60
60
  while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
61
61
  i++;
62
62
  }
63
- if (i >= fileContent.length || fileContent[i] !== "{") {
64
- currentIndex = matchStartIndex + 1;
65
- continue;
66
- }
67
- braceCount = 1;
68
- const secondParamStart = i;
69
- i++;
70
- while (i < fileContent.length && braceCount > 0) {
71
- if (fileContent[i] === "{") braceCount++;
72
- if (fileContent[i] === "}") braceCount--;
73
- i++;
74
- }
75
- if (braceCount !== 0) {
76
- currentIndex = matchStartIndex + 1;
77
- continue;
78
- }
79
- parameter2Text = fileContent.substring(secondParamStart, i);
80
- while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
81
- i++;
82
- }
83
- if (i < fileContent.length && fileContent[i] === ",") {
63
+ if (i >= fileContent.length || fileContent[i] === ")") ;
64
+ else if (fileContent[i] === "{") {
65
+ braceCount = 1;
66
+ const secondParamStart = i;
84
67
  i++;
68
+ while (i < fileContent.length && braceCount > 0) {
69
+ if (fileContent[i] === "{") braceCount++;
70
+ if (fileContent[i] === "}") braceCount--;
71
+ i++;
72
+ }
73
+ if (braceCount !== 0) {
74
+ currentIndex = matchStartIndex + 1;
75
+ continue;
76
+ }
77
+ parameter2Text = fileContent.substring(secondParamStart, i);
85
78
  while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
86
79
  i++;
87
80
  }
81
+ if (i < fileContent.length && fileContent[i] === ",") {
82
+ i++;
83
+ while (i < fileContent.length && (fileContent[i] === " " || fileContent[i] === "\n" || fileContent[i] === " ")) {
84
+ i++;
85
+ }
86
+ }
87
+ } else {
88
+ currentIndex = matchStartIndex + 1;
89
+ continue;
88
90
  }
89
91
  } else if (fileContent[i] !== ")") {
90
92
  currentIndex = matchStartIndex + 1;
@@ -527,8 +529,8 @@ async function $LT_ReadConfig(projectPath) {
527
529
  }
528
530
  const userConfig = configModule.default || {};
529
531
  const tn = (userConfig.tagName || "").toLowerCase().replace(/[-_\s]/g, "");
530
- if (tn.includes("langtag")) {
531
- throw new Error('Custom tagName cannot include "langtag"! (It is not recommended for use with libraries)\n');
532
+ if (tn === "langtag" || tn === "lang-tag") {
533
+ throw new Error('Custom tagName cannot be "lang-tag" or "langtag"! (It is not recommended for use with libraries)\n');
532
534
  }
533
535
  return {
534
536
  ...LANG_TAG_DEFAULT_CONFIG,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lang-tag/cli",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -33,7 +33,6 @@ export function {{tagName}}{{#isTypeScript}}<T extends LangTagTranslations>{{/is
33
33
  // transform: ({ path, params }) => t(path, params),
34
34
  // });
35
35
  // };
36
- //
37
36
  // // SSR
38
37
  // const initT = async (language?: string) => {
39
38
  // const { t } = await initTranslations({ language, namespaces: [namespace] });
@@ -51,22 +50,18 @@ export function {{tagName}}{{#isTypeScript}}<T extends LangTagTranslations>{{/is
51
50
  });
52
51
  };
53
52
 
54
- const client = () => {
55
- {{#isReact}}
56
- return useMemo(() => createTranslations(), []);
57
- {{/isReact}}
58
- {{^isReact}}
59
- return createTranslations();
60
- {{/isReact}}
61
- };
62
-
63
- const server = () => {
64
- return createTranslations();
65
- };
66
-
67
53
  return {
68
- client,
69
- server,
54
+ client: () => {
55
+ {{#isReact}}
56
+ return useMemo(() => createTranslations(), []);
57
+ {{/isReact}}
58
+ {{^isReact}}
59
+ return createTranslations();
60
+ {{/isReact}}
61
+ },
62
+ server: () => {
63
+ return createTranslations();
64
+ },
70
65
  {{#isTypeScript}}
71
66
  Type: {} as CallableTranslations<T>,
72
67
  {{/isTypeScript}}
@@ -40,45 +40,36 @@ export function {{tagName}}<T extends LangTagTranslations>(
40
40
 
41
41
  {{#isReact}}
42
42
  const Context = createContext{{#isTypeScript}}<CallableTranslations<T> | null>{{/isTypeScript}}(null);
43
-
44
- const useTranslations = () => {
45
- const contextTranslations = useContext(Context);
46
- return useMemo(() => createTranslationHelper(contextTranslations), [contextTranslations]);
47
- };
48
-
49
- const initTranslations = (translations{{#isTypeScript}}?: PartialFlexibleTranslations<T>{{/isTypeScript}}) => {
50
- const normalized = useMemo(
51
- () => translations ? normalizeTranslations(translations) : null,
52
- [translations]
53
- );
54
-
55
- return useMemo(() => createTranslationHelper(normalized), [normalized]);
56
- };
57
-
58
- function Provider({translations, children}{{#isTypeScript}}: { translations?: PartialFlexibleTranslations<T>; children: ReactNode }{{/isTypeScript}}){{#isTypeScript}}: ReactNode{{/isTypeScript}} {
59
- const normalized = useMemo(
60
- () => translations ? normalizeTranslations(translations) : null,
61
- [translations]
62
- );
63
-
64
- return <Context.Provider value={normalized}>{children}</Context.Provider>;
65
- }
66
- {{/isReact}}
67
- {{^isReact}}
68
- const initTranslations = (translations{{#isTypeScript}}?: PartialFlexibleTranslations<T>{{/isTypeScript}}) => {
69
- const normalized = translations ? normalizeTranslations(translations) : null;
70
- return createTranslationHelper(normalized);
71
- };
72
43
  {{/isReact}}
73
44
 
74
45
  return {
75
46
  {{#isReact}}
76
- useTranslations,
77
- initTranslations,
78
- Provider,
47
+ useTranslations: () => {
48
+ const contextTranslations = useContext(Context);
49
+ return useMemo(() => createTranslationHelper(contextTranslations), [contextTranslations]);
50
+ },
51
+ initTranslations: (translations{{#isTypeScript}}?: PartialFlexibleTranslations<T>{{/isTypeScript}}) => {
52
+ const normalized = useMemo(
53
+ () => translations ? normalizeTranslations(translations) : null,
54
+ [translations]
55
+ );
56
+
57
+ return useMemo(() => createTranslationHelper(normalized), [normalized]);
58
+ },
59
+ Provider({translations, children}{{#isTypeScript}}: { translations?: PartialFlexibleTranslations<T>; children: ReactNode }{{/isTypeScript}}){{#isTypeScript}}: ReactNode{{/isTypeScript}} {
60
+ const normalized = useMemo(
61
+ () => translations ? normalizeTranslations(translations) : null,
62
+ [translations]
63
+ );
64
+
65
+ return <Context.Provider value={normalized}>{children}</Context.Provider>;
66
+ },
79
67
  {{/isReact}}
80
68
  {{^isReact}}
81
- initTranslations,
69
+ initTranslations: (translations{{#isTypeScript}}?: PartialFlexibleTranslations<T>{{/isTypeScript}}) => {
70
+ const normalized = translations ? normalizeTranslations(translations) : null;
71
+ return createTranslationHelper(normalized);
72
+ };,
82
73
  {{/isReact}}
83
74
  {{#isTypeScript}}
84
75
  InputType: {} as PartialFlexibleTranslations<T>,