gtx-cli 2.1.15 → 2.1.17

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.1.17
4
+
5
+ ### Patch Changes
6
+
7
+ - [#635](https://github.com/generaltranslation/gt/pull/635) [`10aa051`](https://github.com/generaltranslation/gt/commit/10aa051592cea43f772615da200c8615d4dd1a78) Thanks [@brian-lou](https://github.com/brian-lou)! - Create dictionary with uuid to reduce flakiness
8
+
9
+ ## 2.1.16
10
+
11
+ ### Patch Changes
12
+
13
+ - [#630](https://github.com/generaltranslation/gt/pull/630) [`1f0dc1b`](https://github.com/generaltranslation/gt/commit/1f0dc1b17f22737263938998f5c516e0aa136b1a) Thanks [@fernando-aviles](https://github.com/fernando-aviles)! - Adding localization of import paths to MDX files
14
+
3
15
  ## 2.1.15
4
16
 
5
17
  ### Patch Changes
@@ -7,6 +7,7 @@ import flattenJsonFiles from '../../utils/flattenJsonFiles.js';
7
7
  import localizeStaticUrls from '../../utils/localizeStaticUrls.js';
8
8
  import processAnchorIds from '../../utils/processAnchorIds.js';
9
9
  import { noFilesError, noVersionIdError } from '../../console/index.js';
10
+ import localizeStaticImports from '../../utils/localizeStaticImports.js';
10
11
  // Downloads translations that were completed
11
12
  export async function handleTranslate(options, settings, filesTranslationResponse) {
12
13
  if (filesTranslationResponse && settings.files) {
@@ -46,6 +47,10 @@ export async function postProcessTranslations(settings) {
46
47
  // Uses inline {#id} format by default, or div wrapping if experimentalAddHeaderAnchorIds is 'mintlify'
47
48
  await processAnchorIds(settings);
48
49
  }
50
+ // Localize static imports (import Snippet from /snippets/file.mdx -> import Snippet from /snippets/[locale]/file.mdx)
51
+ if (settings.options?.experimentalLocalizeStaticImports) {
52
+ await localizeStaticImports(settings);
53
+ }
49
54
  // Flatten json files into a single file
50
55
  if (settings.options?.experimentalFlattenJsonFiles) {
51
56
  await flattenJsonFiles(settings);
@@ -7,6 +7,7 @@ import loadJSON from '../../fs/loadJSON.js';
7
7
  import { hashSource } from 'generaltranslation/id';
8
8
  import getEntryAndMetadata from '../utils/getEntryAndMetadata.js';
9
9
  import { logError } from '../../console/logging.js';
10
+ import { randomUUID } from 'node:crypto';
10
11
  export async function createDictionaryUpdates(dictionaryPath, esbuildConfig) {
11
12
  let dictionary;
12
13
  // ---- HANDLE JSON STRING DICTIONARY ----- //
@@ -21,7 +22,7 @@ export async function createDictionaryUpdates(dictionaryPath, esbuildConfig) {
21
22
  write: false,
22
23
  });
23
24
  const bundledCode = result.outputFiles[0].text;
24
- const tempFilePath = path.join(os.tmpdir(), 'bundled-dictionary.js');
25
+ const tempFilePath = path.join(os.tmpdir(), `bundled-dictionary-${randomUUID()}.js`);
25
26
  await fs.promises.writeFile(tempFilePath, bundledCode);
26
27
  // Load the module using dynamic import
27
28
  let dictionaryModule;
@@ -1,13 +1,24 @@
1
1
  import * as fs from 'fs';
2
+ import * as path from 'path';
2
3
  import { createFileMapping } from '../formats/files/fileMapping.js';
3
4
  import micromatch from 'micromatch';
4
5
  import { unified } from 'unified';
5
6
  import remarkParse from 'remark-parse';
6
7
  import remarkMdx from 'remark-mdx';
7
8
  import remarkFrontmatter from 'remark-frontmatter';
8
- import remarkStringify from 'remark-stringify';
9
9
  import { visit } from 'unist-util-visit';
10
10
  const { isMatch } = micromatch;
11
+ /**
12
+ * Checks if a file exists at the given path
13
+ */
14
+ function fileExists(filePath) {
15
+ try {
16
+ return fs.existsSync(filePath);
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
11
22
  /**
12
23
  * Localizes static imports in content files.
13
24
  * Currently only supported for md and mdx files. (/docs/ -> /[locale]/docs/)
@@ -48,7 +59,7 @@ export default async function localizeStaticImports(settings) {
48
59
  const fileContent = await fs.promises.readFile(filePath, 'utf8');
49
60
  // Localize the file using default locale
50
61
  const localizedFile = localizeStaticImportsForFile(fileContent, settings.defaultLocale, settings.defaultLocale, // Process as default locale
51
- settings.options?.docsHideDefaultLocaleImport || false, settings.options?.docsImportPattern, settings.options?.excludeStaticImports, filePath.endsWith('.md'));
62
+ settings.options?.docsHideDefaultLocaleImport || false, settings.options?.docsImportPattern, settings.options?.excludeStaticImports, filePath);
52
63
  // Write the localized file back to the same path
53
64
  await fs.promises.writeFile(filePath, localizedFile);
54
65
  }));
@@ -64,7 +75,7 @@ export default async function localizeStaticImports(settings) {
64
75
  // Get file content
65
76
  const fileContent = await fs.promises.readFile(filePath, 'utf8');
66
77
  // Localize the file
67
- const localizedFile = localizeStaticImportsForFile(fileContent, settings.defaultLocale, locale, settings.options?.docsHideDefaultLocaleImport || false, settings.options?.docsImportPattern, settings.options?.excludeStaticImports, filePath.endsWith('.md'));
78
+ const localizedFile = localizeStaticImportsForFile(fileContent, settings.defaultLocale, locale, settings.options?.docsHideDefaultLocaleImport || false, settings.options?.docsImportPattern, settings.options?.excludeStaticImports, filePath);
68
79
  // Write the localized file to the target path
69
80
  await fs.promises.writeFile(filePath, localizedFile);
70
81
  }));
@@ -175,23 +186,45 @@ function transformNonDefaultLocaleImportPath(fullPath, patternHead, targetLocale
175
186
  /**
176
187
  * Main import path transformation function that delegates to specific scenarios
177
188
  */
178
- function transformImportPath(fullPath, patternHead, targetLocale, defaultLocale, hideDefaultLocale) {
189
+ function transformImportPath(fullPath, patternHead, targetLocale, defaultLocale, hideDefaultLocale, currentFilePath, projectRoot = process.cwd() // fallback if not provided
190
+ ) {
191
+ let newPath;
179
192
  if (targetLocale === defaultLocale) {
180
- return transformDefaultLocaleImportPath(fullPath, patternHead, defaultLocale, hideDefaultLocale);
193
+ newPath = transformDefaultLocaleImportPath(fullPath, patternHead, defaultLocale, hideDefaultLocale);
181
194
  }
182
195
  else if (hideDefaultLocale) {
183
- return transformNonDefaultLocaleImportPathWithHidden(fullPath, patternHead, targetLocale, defaultLocale);
196
+ newPath = transformNonDefaultLocaleImportPathWithHidden(fullPath, patternHead, targetLocale, defaultLocale);
184
197
  }
185
198
  else {
186
- return transformNonDefaultLocaleImportPath(fullPath, patternHead, targetLocale, defaultLocale);
199
+ newPath = transformNonDefaultLocaleImportPath(fullPath, patternHead, targetLocale, defaultLocale);
187
200
  }
201
+ if (!newPath)
202
+ return null;
203
+ if (currentFilePath) {
204
+ let resolvedPath;
205
+ if (newPath.startsWith('/')) {
206
+ // Interpret as project-root relative
207
+ resolvedPath = path.join(projectRoot, newPath.replace(/^\//, ''));
208
+ }
209
+ else {
210
+ // Relative to current file
211
+ const currentDir = path.dirname(currentFilePath);
212
+ resolvedPath = path.resolve(currentDir, newPath);
213
+ }
214
+ const pathExists = fileExists(resolvedPath);
215
+ if (!pathExists) {
216
+ return null;
217
+ }
218
+ }
219
+ return newPath;
188
220
  }
189
221
  /**
190
222
  * AST-based transformation for MDX files using remark-mdx
191
223
  */
192
- function transformMdxImports(mdxContent, defaultLocale, targetLocale, hideDefaultLocale, pattern = '/[locale]', exclude = []) {
224
+ function transformMdxImports(mdxContent, defaultLocale, targetLocale, hideDefaultLocale, pattern = '/[locale]', exclude = [], currentFilePath) {
193
225
  const transformedImports = [];
194
- if (!pattern.startsWith('/')) {
226
+ // Don't auto-prefix relative patterns that start with . or ..
227
+ if (!pattern.startsWith('/') && !pattern.startsWith('.')) {
195
228
  pattern = '/' + pattern;
196
229
  }
197
230
  const patternHead = pattern.split('[locale]')[0];
@@ -239,23 +272,23 @@ function transformMdxImports(mdxContent, defaultLocale, targetLocale, hideDefaul
239
272
  transformedImports: [],
240
273
  };
241
274
  }
242
- // Visit only mdxjsEsm nodes (import/export statements)
275
+ let content = mdxContent;
276
+ // Visit only mdxjsEsm nodes (import/export statements) to collect replacements
243
277
  visit(processedAst, 'mdxjsEsm', (node) => {
244
278
  if (node.value && node.value.includes(patternHead.replace(/\/$/, ''))) {
245
- // Find and transform import paths in the node value
279
+ // Find import lines that need transformation
246
280
  const lines = node.value.split('\n');
247
- const transformedLines = lines.map((line) => {
281
+ lines.forEach((line) => {
248
282
  // Only process import lines that match our pattern
249
283
  if (!line.trim().startsWith('import ')) {
250
- return line;
284
+ return;
251
285
  }
252
286
  // Check if this line should be processed
253
287
  if (!shouldProcessImportPath(line, patternHead, targetLocale, defaultLocale)) {
254
- return line;
288
+ return;
255
289
  }
256
290
  // Extract the path from the import statement
257
291
  const quotes = ['"', "'", '`'];
258
- let transformedLine = line;
259
292
  for (const quote of quotes) {
260
293
  // Try both with and without trailing slash
261
294
  let startPattern = `${quote}${patternHead}`;
@@ -274,7 +307,7 @@ function transformMdxImports(mdxContent, defaultLocale, targetLocale, hideDefaul
274
307
  continue;
275
308
  const fullPath = line.slice(pathStart, pathEnd);
276
309
  // Transform the import path
277
- const newPath = transformImportPath(fullPath, patternHead, targetLocale, defaultLocale, hideDefaultLocale);
310
+ const newPath = transformImportPath(fullPath, patternHead, targetLocale, defaultLocale, hideDefaultLocale, currentFilePath);
278
311
  if (!newPath) {
279
312
  continue; // No transformation needed
280
313
  }
@@ -282,43 +315,15 @@ function transformMdxImports(mdxContent, defaultLocale, targetLocale, hideDefaul
282
315
  if (isImportPathExcluded(fullPath, exclude, defaultLocale)) {
283
316
  continue;
284
317
  }
285
- // Apply the transformation
318
+ // Apply the transformation to the original content
319
+ // Simply replace the import path with the new path
320
+ content = content.replace(`${quote}${fullPath}${quote}`, `${quote}${newPath}${quote}`);
286
321
  transformedImports.push({ originalPath: fullPath, newPath });
287
- transformedLine =
288
- line.slice(0, pathStart) + newPath + line.slice(pathEnd);
289
322
  break;
290
323
  }
291
- return transformedLine;
292
324
  });
293
- node.value = transformedLines.join('\n');
294
325
  }
295
326
  });
296
- // Convert the modified AST back to MDX string
297
- let content;
298
- try {
299
- const stringifyProcessor = unified()
300
- .use(remarkStringify)
301
- .use(remarkFrontmatter, ['yaml', 'toml'])
302
- .use(remarkMdx);
303
- content = stringifyProcessor.stringify(processedAst);
304
- }
305
- catch (error) {
306
- console.warn(`Failed to stringify MDX content: ${error instanceof Error ? error.message : String(error)}`);
307
- console.warn('Returning original content unchanged due to stringify error.');
308
- return {
309
- content: mdxContent,
310
- hasChanges: false,
311
- transformedImports: [],
312
- };
313
- }
314
- // Handle newline formatting to match original input
315
- if (content.endsWith('\n') && !mdxContent.endsWith('\n')) {
316
- content = content.slice(0, -1);
317
- }
318
- // Preserve leading newlines from original content
319
- if (mdxContent.startsWith('\n') && !content.startsWith('\n')) {
320
- content = '\n' + content;
321
- }
322
327
  return {
323
328
  content,
324
329
  hasChanges: transformedImports.length > 0,
@@ -329,12 +334,12 @@ function transformMdxImports(mdxContent, defaultLocale, targetLocale, hideDefaul
329
334
  * AST-based transformation for MDX files only
330
335
  */
331
336
  function localizeStaticImportsForFile(file, defaultLocale, targetLocale, hideDefaultLocale, pattern = '/[locale]', // eg /docs/[locale] or /[locale]
332
- exclude = [], isMarkdown = false) {
337
+ exclude = [], currentFilePath) {
333
338
  // Skip .md files entirely - they cannot have imports
334
- if (isMarkdown) {
339
+ if (currentFilePath && currentFilePath.endsWith('.md')) {
335
340
  return file;
336
341
  }
337
342
  // For MDX files, use AST-based transformation
338
- const result = transformMdxImports(file, defaultLocale, targetLocale, hideDefaultLocale, pattern, exclude);
343
+ const result = transformMdxImports(file, defaultLocale, targetLocale, hideDefaultLocale, pattern, exclude, currentFilePath);
339
344
  return result.content;
340
345
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "2.1.15",
3
+ "version": "2.1.17",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [