gtx-cli 2.1.5 → 2.1.7

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/api/checkFileTranslations.d.ts +1 -4
  3. package/dist/api/checkFileTranslations.js +32 -35
  4. package/dist/api/downloadFileBatch.d.ts +1 -0
  5. package/dist/api/sendFiles.d.ts +2 -13
  6. package/dist/api/sendFiles.js +15 -8
  7. package/dist/cli/base.d.ts +5 -17
  8. package/dist/cli/base.js +48 -68
  9. package/dist/cli/commands/stage.d.ts +5 -0
  10. package/dist/cli/commands/stage.js +100 -0
  11. package/dist/cli/commands/translate.d.ts +6 -0
  12. package/dist/cli/commands/translate.js +53 -0
  13. package/dist/cli/flags.d.ts +3 -0
  14. package/dist/cli/flags.js +37 -0
  15. package/dist/cli/next.js +0 -1
  16. package/dist/cli/react.d.ts +2 -5
  17. package/dist/cli/react.js +13 -153
  18. package/dist/config/generateSettings.js +11 -6
  19. package/dist/console/index.d.ts +1 -0
  20. package/dist/console/index.js +1 -0
  21. package/dist/console/logging.d.ts +1 -0
  22. package/dist/console/logging.js +3 -0
  23. package/dist/formats/files/fileMapping.d.ts +2 -1
  24. package/dist/formats/files/fileMapping.js +6 -0
  25. package/dist/formats/files/translate.d.ts +4 -13
  26. package/dist/formats/files/translate.js +30 -142
  27. package/dist/formats/files/upload.js +1 -75
  28. package/dist/formats/gt/save.d.ts +1 -2
  29. package/dist/formats/gt/save.js +2 -5
  30. package/dist/fs/config/setupConfig.d.ts +1 -0
  31. package/dist/fs/config/setupConfig.js +1 -0
  32. package/dist/fs/config/updateConfig.d.ts +1 -2
  33. package/dist/fs/config/updateConfig.js +1 -1
  34. package/dist/fs/config/updateVersions.d.ts +10 -0
  35. package/dist/fs/config/updateVersions.js +30 -0
  36. package/dist/fs/copyFile.d.ts +1 -2
  37. package/dist/translation/parse.d.ts +2 -2
  38. package/dist/translation/parse.js +4 -6
  39. package/dist/translation/stage.d.ts +2 -5
  40. package/dist/translation/stage.js +13 -55
  41. package/dist/types/files.d.ts +1 -0
  42. package/dist/types/files.js +1 -0
  43. package/dist/types/index.d.ts +28 -3
  44. package/dist/utils/flattenJsonFiles.d.ts +2 -2
  45. package/dist/utils/hash.d.ts +6 -0
  46. package/dist/utils/hash.js +11 -0
  47. package/dist/utils/localizeStaticImports.d.ts +2 -2
  48. package/dist/utils/localizeStaticUrls.d.ts +6 -2
  49. package/dist/utils/localizeStaticUrls.js +132 -103
  50. package/package.json +2 -2
  51. package/dist/api/downloadFile.d.ts +0 -9
  52. package/dist/api/downloadFile.js +0 -74
  53. package/dist/api/fetchTranslations.d.ts +0 -7
  54. package/dist/api/fetchTranslations.js +0 -18
  55. package/dist/api/sendUpdates.d.ts +0 -19
  56. package/dist/api/sendUpdates.js +0 -48
  57. package/dist/api/waitForUpdates.d.ts +0 -9
  58. package/dist/api/waitForUpdates.js +0 -89
  59. package/dist/translation/translate.d.ts +0 -2
  60. package/dist/translation/translate.js +0 -18
@@ -0,0 +1,37 @@
1
+ import findFilepath from '../fs/findFilepath.js';
2
+ const DEFAULT_TIMEOUT = 600;
3
+ export function attachTranslateFlags(command) {
4
+ command
5
+ .option('-c, --config <path>', 'Filepath to config file, by default gt.config.json', findFilepath(['gt.config.json']))
6
+ .option('--api-key <key>', 'API key for General Translation cloud service')
7
+ .option('--project-id <id>', 'General Translation project ID')
8
+ .option('--version-id <id>', 'General Translation version ID')
9
+ .option('--default-language, --default-locale <locale>', 'Default locale (e.g., en)')
10
+ .option('--new, --locales <locales...>', 'Space-separated list of locales (e.g., en fr es)')
11
+ .option('--dry-run', 'Dry run, do not send updates to the General Translation API', false)
12
+ .option('--timeout <seconds>', 'Translation wait timeout in seconds', (value) => {
13
+ const parsedValue = parseInt(value, 10);
14
+ if (isNaN(parsedValue)) {
15
+ throw new Error('Not a number.');
16
+ }
17
+ if (parsedValue < 0) {
18
+ throw new Error('Timeout must be a positive number.');
19
+ }
20
+ return parsedValue;
21
+ }, DEFAULT_TIMEOUT)
22
+ .option('--publish', 'Publish translations to the CDN', false)
23
+ .option('--experimental-localize-static-urls', 'Triggering this will run a script after the cli tool that localizes all urls in content files. Currently only supported for md and mdx files.', false)
24
+ .option('--experimental-hide-default-locale', 'When localizing static locales, hide the default locale from the path', false)
25
+ .option('--experimental-flatten-json-files', 'Triggering this will flatten the json files into a single file. This is useful for projects that have a lot of json files.', false)
26
+ .option('--experimental-localize-static-imports', 'Triggering this will run a script after the cli tool that localizes all static imports in content files. Currently only supported for md and mdx files.', false);
27
+ return command;
28
+ }
29
+ export function attachAdditionalReactTranslateFlags(command) {
30
+ command
31
+ .option('--tsconfig, --jsconfig <path>', 'Path to custom jsconfig or tsconfig file', findFilepath(['./tsconfig.json', './jsconfig.json']))
32
+ .option('--dictionary <path>', 'Path to dictionary file')
33
+ .option('--src <paths...>', "Space-separated list of glob patterns containing the app's source code, by default 'src/**/*.{js,jsx,ts,tsx}' 'app/**/*.{js,jsx,ts,tsx}' 'pages/**/*.{js,jsx,ts,tsx}' 'components/**/*.{js,jsx,ts,tsx}'")
34
+ .option('--inline', 'Include inline <T> tags in addition to dictionary file', true)
35
+ .option('--ignore-errors', 'Ignore errors encountered while scanning for <T> tags', false);
36
+ return command;
37
+ }
package/dist/cli/next.js CHANGED
@@ -8,7 +8,6 @@ export class NextCLI extends ReactCLI {
8
8
  init() {
9
9
  this.setupStageCommand();
10
10
  this.setupTranslateCommand();
11
- this.setupScanCommand();
12
11
  this.setupGenerateSourceCommand();
13
12
  this.setupValidateCommand();
14
13
  }
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { Options, SupportedFrameworks, WrapOptions, GenerateSourceOptions, SupportedLibraries } from '../types/index.js';
2
+ import { Options, SupportedFrameworks, WrapOptions, SupportedLibraries, TranslateFlags } from '../types/index.js';
3
3
  import { BaseCLI } from './base.js';
4
4
  export declare class ReactCLI extends BaseCLI {
5
5
  constructor(command: Command, library: 'gt-react' | 'gt-next', additionalModules?: SupportedLibraries[]);
@@ -12,10 +12,7 @@ export declare class ReactCLI extends BaseCLI {
12
12
  protected setupTranslateCommand(): void;
13
13
  protected setupValidateCommand(): void;
14
14
  protected setupGenerateSourceCommand(): void;
15
- protected setupScanCommand(): void;
16
- protected handleGenerateSourceCommand(initOptions: GenerateSourceOptions): Promise<void>;
15
+ protected handleGenerateSourceCommand(initOptions: TranslateFlags): Promise<void>;
17
16
  protected handleScanCommand(options: WrapOptions): Promise<void>;
18
- protected handleStage(initOptions: Options): Promise<void>;
19
- protected handleTranslate(initOptions: Options): Promise<void>;
20
17
  protected handleValidate(initOptions: Options, files?: string[]): Promise<void>;
21
18
  }
package/dist/cli/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { displayHeader, endCommand, logError, logErrorAndExit, logStep, logSuccess, logWarning, promptConfirm, } from '../console/logging.js';
1
+ import { displayHeader, endCommand, logError, logStep, logSuccess, logWarning, promptConfirm, } from '../console/logging.js';
2
2
  import loadJSON from '../fs/loadJSON.js';
3
3
  import findFilepath from '../fs/findFilepath.js';
4
4
  import chalk from 'chalk';
@@ -8,15 +8,12 @@ import { wrapContentReact } from '../react/parse/wrapContent.js';
8
8
  import { generateSettings } from '../config/generateSettings.js';
9
9
  import { saveJSON } from '../fs/saveJSON.js';
10
10
  import { resolveLocaleFiles } from '../fs/config/parseFilesConfig.js';
11
- import { noFilesError, noVersionIdError } from '../console/index.js';
12
- import { stageProject } from '../translation/stage.js';
13
- import { createUpdates } from '../translation/parse.js';
14
- import { translate } from '../translation/translate.js';
15
- import updateConfig from '../fs/config/updateConfig.js';
11
+ import { noFilesError } from '../console/index.js';
12
+ import { aggregateReactTranslations } from '../translation/stage.js';
16
13
  import { validateConfigExists } from '../config/validateSettings.js';
17
14
  import { validateProject } from '../translation/validate.js';
18
15
  import { intro } from '@clack/prompts';
19
- const DEFAULT_TIMEOUT = 600;
16
+ import { attachAdditionalReactTranslateFlags, attachTranslateFlags, } from './flags.js';
20
17
  const pkg = 'gt-react';
21
18
  export class ReactCLI extends BaseCLI {
22
19
  constructor(command, library, additionalModules) {
@@ -25,7 +22,6 @@ export class ReactCLI extends BaseCLI {
25
22
  init() {
26
23
  this.setupStageCommand();
27
24
  this.setupTranslateCommand();
28
- this.setupScanCommand();
29
25
  this.setupGenerateSourceCommand();
30
26
  this.setupValidateCommand();
31
27
  }
@@ -36,50 +32,18 @@ export class ReactCLI extends BaseCLI {
36
32
  return wrapContentReact(options, pkg, framework, errors, warnings);
37
33
  }
38
34
  setupStageCommand() {
39
- this.program
35
+ attachAdditionalReactTranslateFlags(attachTranslateFlags(this.program
40
36
  .command('stage')
41
- .description('Submits the project to the General Translation API for translation. Translations created using this command will require human approval.')
42
- .option('-c, --config <path>', 'Filepath to config file, by default gt.config.json', findFilepath(['gt.config.json']))
43
- .option('--api-key <key>', 'API key for General Translation cloud service')
44
- .option('--project-id <id>', 'Project ID for the translation service')
45
- .option('--version-id <id>', 'Version ID for the translation service')
46
- .option('--tsconfig, --jsconfig <path>', 'Path to jsconfig or tsconfig file', findFilepath(['./tsconfig.json', './jsconfig.json']))
47
- .option('--dictionary <path>', 'Path to dictionary file')
48
- .option('--src <paths...>', "Space-separated list of glob patterns containing the app's source code, by default 'src/**/*.{js,jsx,ts,tsx}' 'app/**/*.{js,jsx,ts,tsx}' 'pages/**/*.{js,jsx,ts,tsx}' 'components/**/*.{js,jsx,ts,tsx}'")
49
- .option('--default-language, --default-locale <locale>', 'Default locale (e.g., en)')
50
- .option('--new, --locales <locales...>', 'Space-separated list of locales (e.g., en fr es)')
51
- .option('--inline', 'Include inline <T> tags in addition to dictionary file', true)
52
- .option('--ignore-errors', 'Ignore errors encountered while scanning for <T> tags', false)
53
- .option('--dry-run', 'Dry run, does not send updates to General Translation API', false)
54
- .option('--timeout <seconds>', 'Timeout in seconds for waiting for updates to be deployed to the CDN', DEFAULT_TIMEOUT.toString())
55
- .action(async (options) => {
56
- displayHeader('Staging project for translation with approval...');
37
+ .description('Submits the project to the General Translation API for translation. Translations created using this command will require human approval.'))).action(async (options) => {
38
+ displayHeader('Staging project for translation with approval required...');
57
39
  await this.handleStage(options);
58
40
  endCommand('Done!');
59
41
  });
60
42
  }
61
43
  setupTranslateCommand() {
62
- this.program
44
+ attachAdditionalReactTranslateFlags(attachTranslateFlags(this.program
63
45
  .command('translate')
64
- .description('Scans the project for a dictionary and/or <T> tags, and sends the updates to the General Translation API for translation.')
65
- .option('-c, --config <path>', 'Filepath to config file, by default gt.config.json', findFilepath(['gt.config.json']))
66
- .option('--api-key <key>', 'API key for General Translation cloud service')
67
- .option('--project-id <id>', 'Project ID for the translation service')
68
- .option('--version-id <id>', 'Version ID for the translation service')
69
- .option('--tsconfig, --jsconfig <path>', 'Path to jsconfig or tsconfig file', findFilepath(['./tsconfig.json', './jsconfig.json']))
70
- .option('--dictionary <path>', 'Path to dictionary file')
71
- .option('--src <paths...>', "Space-separated list of glob patterns containing the app's source code, by default 'src/**/*.{js,jsx,ts,tsx}' 'app/**/*.{js,jsx,ts,tsx}' 'pages/**/*.{js,jsx,ts,tsx}' 'components/**/*.{js,jsx,ts,tsx}'")
72
- .option('--default-language, --default-locale <locale>', 'Default locale (e.g., en)')
73
- .option('--new, --locales <locales...>', 'Space-separated list of locales (e.g., en fr es)')
74
- .option('--inline', 'Include inline <T> tags in addition to dictionary file', true)
75
- .option('--ignore-errors', 'Ignore errors encountered while scanning for <T> tags', false)
76
- .option('--dry-run', 'Dry run, does not send updates to General Translation API', false)
77
- .option('--timeout <seconds>', 'Timeout in seconds for waiting for updates to be deployed to the CDN', DEFAULT_TIMEOUT.toString())
78
- .option('--experimental-localize-static-urls', 'Triggering this will run a script after the cli tool that localizes all urls in content files. Currently only supported for md and mdx files.', false)
79
- .option('--experimental-hide-default-locale', 'When localizing static locales, hide the default locale from the path', false)
80
- .option('--experimental-flatten-json-files', 'Triggering this will flatten the json files into a single file. This is useful for projects that have a lot of json files.', false)
81
- .option('--experimental-localize-static-imports', 'Triggering this will run a script after the cli tool that localizes all static imports in content files. Currently only supported for md and mdx files.', false)
82
- .action(async (options) => {
46
+ .description('Scans the project for a dictionary and/or <T> tags, and sends the updates to the General Translation API for translation.'))).action(async (options) => {
83
47
  displayHeader('Translating project...');
84
48
  await this.handleTranslate(options);
85
49
  endCommand('Done!');
@@ -102,79 +66,17 @@ export class ReactCLI extends BaseCLI {
102
66
  });
103
67
  }
104
68
  setupGenerateSourceCommand() {
105
- this.program
69
+ attachAdditionalReactTranslateFlags(attachTranslateFlags(this.program
106
70
  .command('generate')
107
- .description('Generate a translation file for the source locale. The -t flag must be provided. This command should be used if you are handling your own translations.')
108
- .option('--src <paths...>', "Space-separated list of glob patterns containing the app's source code, by default 'src/**/*.{js,jsx,ts,tsx}' 'app/**/*.{js,jsx,ts,tsx}' 'pages/**/*.{js,jsx,ts,tsx}' 'components/**/*.{js,jsx,ts,tsx}'")
109
- .option('--tsconfig, --jsconfig <path>', 'Path to jsconfig or tsconfig file', findFilepath(['./tsconfig.json', './jsconfig.json']))
110
- .option('--dictionary <path>', 'Path to dictionary file')
111
- .option('--default-language, --default-locale <locale>', 'Source locale (e.g., en)')
112
- .option('--inline', 'Include inline <T> tags in addition to dictionary file', true)
113
- .option('--ignore-errors', 'Ignore errors encountered while scanning for <T> tags', false)
114
- .option('--suppress-warnings', 'Suppress warnings encountered while scanning for <T> tags', false)
115
- .option('-t, --translations-dir, --translation-dir <path>', 'Path to directory where translations will be saved. If this flag is not provided, translations will not be saved locally.')
116
- .action(async (options) => {
71
+ .description('Generate a translation file for the source locale. This command should be used if you are handling your own translations.'))).action(async (initOptions) => {
117
72
  displayHeader('Generating source templates...');
118
- await this.handleGenerateSourceCommand(options);
119
- endCommand('Done!');
120
- });
121
- }
122
- setupScanCommand() {
123
- this.program
124
- .command('scan')
125
- .description('Scans the project and wraps all JSX elements in the src directory with a <T> tag, with unique ids')
126
- .option('--src <paths...>', "Space-separated list of glob patterns containing the app's source code, by default 'src/**/*.{js,jsx,ts,tsx}' 'app/**/*.{js,jsx,ts,tsx}' 'pages/**/*.{js,jsx,ts,tsx}' 'components/**/*.{js,jsx,ts,tsx}'")
127
- .option('-c, --config <path>', 'Filepath to config file, by default gt.config.json', findFilepath(['gt.config.json']))
128
- .option('--disable-ids', 'Disable id generation for the <T> tags', false)
129
- .option('--disable-formatting', 'Disable formatting of edited files', false)
130
- .option('--skip-ts', 'Skip wrapping <T> tags in TypeScript files', false)
131
- .action(async (options) => {
132
- displayHeader('Scanning project...');
133
- await this.handleScanCommand(options);
73
+ await this.handleGenerateSourceCommand(initOptions);
134
74
  endCommand('Done!');
135
75
  });
136
76
  }
137
77
  async handleGenerateSourceCommand(initOptions) {
138
78
  const settings = await generateSettings(initOptions);
139
- const options = { ...initOptions, ...settings };
140
- if (!options.dictionary) {
141
- options.dictionary = findFilepath([
142
- './dictionary.js',
143
- './src/dictionary.js',
144
- './dictionary.json',
145
- './src/dictionary.json',
146
- './dictionary.ts',
147
- './src/dictionary.ts',
148
- ]);
149
- }
150
- // User has to provide a dictionary file
151
- // will not read from settings.files.resolvedPaths.json
152
- const { updates, errors, warnings } = await createUpdates(options, options.dictionary, this.library === 'gt-next' ? 'gt-next' : 'gt-react', false);
153
- if (warnings.length > 0) {
154
- if (options.suppressWarnings) {
155
- logWarning(chalk.yellow(`CLI tool encountered ${warnings.length} warnings while scanning for translatable content. ${chalk.gray('To view these warnings, re-run without the --suppress-warnings flag')}`));
156
- }
157
- else {
158
- logWarning(chalk.yellow(`CLI tool encountered ${warnings.length} warnings while scanning for translatable content. ${chalk.gray('To suppress these warnings, re-run with --suppress-warnings')}\n` +
159
- warnings
160
- .map((warning) => chalk.yellow('• Warning: ') + chalk.white(warning))
161
- .join('\n')));
162
- }
163
- }
164
- if (errors.length > 0) {
165
- if (options.ignoreErrors) {
166
- logWarning(chalk.yellow(`CLI tool encountered errors while scanning for translatable content. These components will not be translated.\n` +
167
- errors
168
- .map((error) => chalk.yellow('• Warning: ') + chalk.white(error))
169
- .join('\n')));
170
- }
171
- else {
172
- logErrorAndExit(chalk.red(`CLI tool encountered errors while scanning for translatable content. ${chalk.gray('To ignore these errors, re-run with --ignore-errors')}\n` +
173
- errors
174
- .map((error) => chalk.red('• Error: ') + chalk.white(error))
175
- .join('\n')));
176
- }
177
- }
79
+ const updates = await aggregateReactTranslations(initOptions, settings, this.library === 'gt-next' ? 'gt-next' : 'gt-react');
178
80
  // Convert updates to the proper data format
179
81
  const newData = {};
180
82
  for (const update of updates) {
@@ -255,48 +157,6 @@ export class ReactCLI extends BaseCLI {
255
157
  .join('\n'));
256
158
  }
257
159
  }
258
- async handleStage(initOptions) {
259
- const settings = await generateSettings(initOptions);
260
- // First run the base class's handleTranslate method
261
- const options = { ...initOptions, ...settings };
262
- if (!settings.stageTranslations) {
263
- // Update settings.stageTranslations to true
264
- settings.stageTranslations = true;
265
- await updateConfig({
266
- configFilepath: options.config,
267
- stageTranslations: true,
268
- });
269
- }
270
- const pkg = this.library === 'gt-next' ? 'gt-next' : 'gt-react';
271
- await stageProject(options, pkg);
272
- }
273
- async handleTranslate(initOptions) {
274
- const settings = await generateSettings(initOptions);
275
- // First run the base class's handleTranslate method
276
- const options = { ...initOptions, ...settings };
277
- try {
278
- await super.handleGenericTranslate(options);
279
- // If the base class's handleTranslate completes successfully, continue with ReactCLI-specific code
280
- }
281
- catch {
282
- // Continue with ReactCLI-specific code even if base handleTranslate failed
283
- }
284
- if (!settings.stageTranslations) {
285
- // If stageTranslations is false, stage the project
286
- const pkg = this.library === 'gt-next' ? 'gt-next' : 'gt-react';
287
- const results = await stageProject(options, pkg);
288
- if (results) {
289
- await translate(options, results.versionId);
290
- }
291
- }
292
- else {
293
- if (!settings._versionId) {
294
- logError(noVersionIdError);
295
- process.exit(1);
296
- }
297
- await translate(options, settings._versionId);
298
- }
299
- }
300
160
  async handleValidate(initOptions, files) {
301
161
  validateConfigExists();
302
162
  const settings = await generateSettings(initOptions);
@@ -76,9 +76,14 @@ export async function generateSettings(options, cwd = process.cwd()) {
76
76
  }
77
77
  }
78
78
  // merge options
79
- let mergedOptions = { ...gtConfig, ...options };
79
+ const mergedOptions = { ...gtConfig, ...options };
80
+ // Add defaultLocale if not provided
81
+ mergedOptions.defaultLocale =
82
+ mergedOptions.defaultLocale || libraryDefaultLocale;
80
83
  // merge locales
81
84
  mergedOptions.locales = Array.from(new Set([...(gtConfig.locales || []), ...(options.locales || [])]));
85
+ // Separate defaultLocale from locales
86
+ mergedOptions.locales = mergedOptions.locales.filter((locale) => locale !== mergedOptions.defaultLocale);
82
87
  // Add apiKey if not provided
83
88
  mergedOptions.apiKey = mergedOptions.apiKey || process.env.GT_API_KEY;
84
89
  // Add projectId if not provided
@@ -87,9 +92,6 @@ export async function generateSettings(options, cwd = process.cwd()) {
87
92
  mergedOptions.baseUrl = mergedOptions.baseUrl || defaultBaseUrl;
88
93
  // Add dashboardUrl if not provided
89
94
  mergedOptions.dashboardUrl = mergedOptions.dashboardUrl || GT_DASHBOARD_URL;
90
- // Add defaultLocale if not provided
91
- mergedOptions.defaultLocale =
92
- mergedOptions.defaultLocale || libraryDefaultLocale;
93
95
  // Add locales if not provided
94
96
  mergedOptions.locales = mergedOptions.locales || [];
95
97
  // Add default config file name if not provided
@@ -101,14 +103,16 @@ export async function generateSettings(options, cwd = process.cwd()) {
101
103
  // Add stageTranslations if not provided
102
104
  // For human review, always stage the project
103
105
  mergedOptions.stageTranslations = mergedOptions.stageTranslations ?? false;
106
+ // Add publish if not provided
107
+ mergedOptions.publish = (gtConfig.publish || options.publish) ?? false;
104
108
  // Populate src if not provided
105
109
  mergedOptions.src = mergedOptions.src || DEFAULT_SRC_PATTERNS;
106
110
  // Resolve all glob patterns in the files object
107
111
  mergedOptions.files = mergedOptions.files
108
112
  ? resolveFiles(mergedOptions.files, mergedOptions.defaultLocale, mergedOptions.locales, cwd)
109
113
  : undefined;
110
- mergedOptions = {
111
- ...mergedOptions,
114
+ mergedOptions.options = {
115
+ ...(mergedOptions.options || {}),
112
116
  experimentalLocalizeStaticImports: gtConfig.options?.experimentalLocalizeStaticImports ||
113
117
  options.experimentalLocalizeStaticImports,
114
118
  experimentalLocalizeStaticUrls: gtConfig.options?.experimentalLocalizeStaticUrls ||
@@ -153,6 +157,7 @@ export async function generateSettings(options, cwd = process.cwd()) {
153
157
  framework: mergedOptions.framework,
154
158
  });
155
159
  }
160
+ mergedOptions.configDirectory = path.join(cwd, '.gt');
156
161
  validateSettings(mergedOptions);
157
162
  // Set up GT instance
158
163
  gt.setConfig({
@@ -18,3 +18,4 @@ export declare const noApiKeyError = "No API key found! Please provide an API ke
18
18
  export declare const devApiKeyError = "You are using a development API key. Please use a production API key to use the General Translation API.\nYou can generate a production API key with the command: npx gtx-cli auth -t production";
19
19
  export declare const noProjectIdError = "No project ID found! Please provide a project ID using the --project-id flag, specify it in your gt.config.json file, or set the GT_PROJECT_ID environment variable.";
20
20
  export declare const noVersionIdError = "No version ID found! Please provide a version ID using the --version-id flag or specify it in your gt.config.json file as the _versionId property.";
21
+ export declare const invalidConfigurationError = "Invalid files configuration! Please either provide a valid configuration to download local translations or set the --publish flag to true to upload translations to the CDN.";
@@ -21,3 +21,4 @@ export const noApiKeyError = `No API key found! Please provide an API key using
21
21
  export const devApiKeyError = `You are using a development API key. Please use a production API key to use the General Translation API.\nYou can generate a production API key with the command: npx gtx-cli auth -t production`;
22
22
  export const noProjectIdError = `No project ID found! Please provide a project ID using the --project-id flag, specify it in your gt.config.json file, or set the GT_PROJECT_ID environment variable.`;
23
23
  export const noVersionIdError = `No version ID found! Please provide a version ID using the --version-id flag or specify it in your gt.config.json file as the _versionId property.`;
24
+ export const invalidConfigurationError = `Invalid files configuration! Please either provide a valid configuration to download local translations or set the --publish flag to true to upload translations to the CDN.`;
@@ -13,6 +13,7 @@ export declare function displayProjectId(projectId: string): void;
13
13
  export declare function displayResolvedPaths(resolvedPaths: [string, string][]): void;
14
14
  export declare function displayCreatedConfigFile(configFilepath: string): void;
15
15
  export declare function displayUpdatedConfigFile(configFilepath: string): void;
16
+ export declare function displayUpdatedVersionsFile(versionFilepath: string): void;
16
17
  export declare function createSpinner(indicator?: 'dots' | 'timer'): import("@clack/prompts").SpinnerResult;
17
18
  export declare function createOraSpinner(indicator?: 'dots' | 'circleHalves'): Promise<import("ora").Ora>;
18
19
  export declare function promptText({ message, defaultValue, validate, }: {
@@ -73,6 +73,9 @@ export function displayCreatedConfigFile(configFilepath) {
73
73
  export function displayUpdatedConfigFile(configFilepath) {
74
74
  log.success(`Updated config file ${chalk.cyan(configFilepath)}`);
75
75
  }
76
+ export function displayUpdatedVersionsFile(versionFilepath) {
77
+ log.success(`Updated versions file ${chalk.cyan(versionFilepath)}`);
78
+ }
76
79
  // Spinner functionality
77
80
  export function createSpinner(indicator = 'timer') {
78
81
  return spinner({ indicator });
@@ -1,4 +1,5 @@
1
1
  import { ResolvedFiles, TransformFiles } from '../../types/index.js';
2
+ import { FileMapping } from '../../types/files.js';
2
3
  /**
3
4
  * Creates a mapping between source files and their translated counterparts for each locale
4
5
  * @param filePaths - Resolved file paths for different file types
@@ -7,4 +8,4 @@ import { ResolvedFiles, TransformFiles } from '../../types/index.js';
7
8
  * @param locales - List of locales to create a mapping for
8
9
  * @returns A mapping between source files and their translated counterparts for each locale, in the form of relative paths
9
10
  */
10
- export declare function createFileMapping(filePaths: ResolvedFiles, placeholderPaths: ResolvedFiles, transformPaths: TransformFiles, targetLocales: string[], defaultLocale: string): Record<string, Record<string, string>>;
11
+ export declare function createFileMapping(filePaths: ResolvedFiles, placeholderPaths: ResolvedFiles, transformPaths: TransformFiles, targetLocales: string[], defaultLocale: string): FileMapping;
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { getRelative } from '../../fs/findFilepath.js';
5
5
  import { getLocaleProperties } from 'generaltranslation';
6
6
  import { replaceLocalePlaceholders } from '../utils.js';
7
+ import { TEMPLATE_FILE_NAME } from '../../cli/commands/stage.js';
7
8
  /**
8
9
  * Creates a mapping between source files and their translated counterparts for each locale
9
10
  * @param filePaths - Resolved file paths for different file types
@@ -18,6 +19,11 @@ export function createFileMapping(filePaths, placeholderPaths, transformPaths, t
18
19
  const translatedPaths = resolveLocaleFiles(placeholderPaths, locale);
19
20
  const localeMapping = {};
20
21
  // Process each file type
22
+ // Start with GTJSON Template files
23
+ if (translatedPaths.gt) {
24
+ const filepath = translatedPaths.gt;
25
+ localeMapping[TEMPLATE_FILE_NAME] = filepath;
26
+ }
21
27
  for (const typeIndex of SUPPORTED_FILE_EXTENSIONS) {
22
28
  if (!filePaths[typeIndex] || !translatedPaths[typeIndex])
23
29
  continue;
@@ -1,13 +1,4 @@
1
- import { ResolvedFiles, Settings, TransformFiles } from '../../types/index.js';
2
- import { DataFormat } from '../../types/data.js';
3
- import { TranslateOptions } from '../../cli/base.js';
4
- /**
5
- * Sends multiple files to the API for translation
6
- * @param filePaths - Resolved file paths for different file types
7
- * @param placeholderPaths - Placeholder paths for translated files
8
- * @param transformPaths - Transform paths for file naming
9
- * @param dataFormat - Format of the data within the files
10
- * @param options - Translation options including API settings
11
- * @returns Promise that resolves when translation is complete
12
- */
13
- export declare function translateFiles(filePaths: ResolvedFiles, placeholderPaths: ResolvedFiles, transformPaths: TransformFiles, dataFormat: DataFormat | undefined, options: Settings & TranslateOptions): Promise<void>;
1
+ import { Settings } from '../../types/index.js';
2
+ import { FileToTranslate } from '../../types/data.js';
3
+ export declare const SUPPORTED_DATA_FORMATS: string[];
4
+ export declare function aggregateFiles(settings: Settings): Promise<FileToTranslate[]>;
@@ -1,38 +1,42 @@
1
- import { checkFileTranslations } from '../../api/checkFileTranslations.js';
2
- import { sendFiles } from '../../api/sendFiles.js';
3
- import { noSupportedFormatError, noLocalesError, noDefaultLocaleError, noApiKeyError, noProjectIdError, devApiKeyError, } from '../../console/index.js';
4
- import { logErrorAndExit, createSpinner, logError, logSuccess, } from '../../console/logging.js';
1
+ import { logError } from '../../console/logging.js';
5
2
  import { getRelative, readFile } from '../../fs/findFilepath.js';
6
- import chalk from 'chalk';
7
- import { downloadFile } from '../../api/downloadFile.js';
8
- import { downloadFileBatch } from '../../api/downloadFileBatch.js';
9
3
  import { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';
10
4
  import sanitizeFileContent from '../../utils/sanitizeFileContent.js';
11
5
  import { parseJson } from '../json/parseJson.js';
12
- import { createFileMapping } from './fileMapping.js';
13
6
  import parseYaml from '../yaml/parseYaml.js';
14
- const SUPPORTED_DATA_FORMATS = ['JSX', 'ICU', 'I18NEXT'];
15
- /**
16
- * Sends multiple files to the API for translation
17
- * @param filePaths - Resolved file paths for different file types
18
- * @param placeholderPaths - Placeholder paths for translated files
19
- * @param transformPaths - Transform paths for file naming
20
- * @param dataFormat - Format of the data within the files
21
- * @param options - Translation options including API settings
22
- * @returns Promise that resolves when translation is complete
23
- */
24
- export async function translateFiles(filePaths, placeholderPaths, transformPaths, dataFormat = 'JSX', options) {
25
- // Collect all files to translate
7
+ import { determineLibrary } from '../../fs/determineFramework.js';
8
+ export const SUPPORTED_DATA_FORMATS = ['JSX', 'ICU', 'I18NEXT'];
9
+ export async function aggregateFiles(settings) {
10
+ // Aggregate all files to translate
26
11
  const allFiles = [];
27
- const additionalOptions = options.options || {};
12
+ if (!settings.files ||
13
+ (Object.keys(settings.files.placeholderPaths).length === 1 &&
14
+ settings.files.placeholderPaths.gt)) {
15
+ return allFiles;
16
+ }
17
+ const { resolvedPaths: filePaths } = settings.files;
28
18
  // Process JSON files
29
19
  if (filePaths.json) {
30
- if (!SUPPORTED_DATA_FORMATS.includes(dataFormat)) {
31
- logErrorAndExit(noSupportedFormatError);
20
+ const { library, additionalModules } = determineLibrary();
21
+ // Determine dataFormat for JSONs
22
+ let dataFormat;
23
+ if (library === 'next-intl') {
24
+ dataFormat = 'ICU';
25
+ }
26
+ else if (library === 'i18next') {
27
+ if (additionalModules.includes('i18next-icu')) {
28
+ dataFormat = 'ICU';
29
+ }
30
+ else {
31
+ dataFormat = 'I18NEXT';
32
+ }
33
+ }
34
+ else {
35
+ dataFormat = 'JSX';
32
36
  }
33
37
  const jsonFiles = filePaths.json.map((filePath) => {
34
38
  const content = readFile(filePath);
35
- const parsedJson = parseJson(content, filePath, additionalOptions, options.defaultLocale);
39
+ const parsedJson = parseJson(content, filePath, settings.options || {}, settings.defaultLocale);
36
40
  const relativePath = getRelative(filePath);
37
41
  return {
38
42
  content: parsedJson,
@@ -45,18 +49,14 @@ export async function translateFiles(filePaths, placeholderPaths, transformPaths
45
49
  }
46
50
  // Process YAML files
47
51
  if (filePaths.yaml) {
48
- if (!SUPPORTED_DATA_FORMATS.includes(dataFormat)) {
49
- logErrorAndExit(noSupportedFormatError);
50
- }
51
52
  const yamlFiles = filePaths.yaml.map((filePath) => {
52
53
  const content = readFile(filePath);
53
- const { content: parsedYaml, fileFormat } = parseYaml(content, filePath, additionalOptions);
54
+ const { content: parsedYaml, fileFormat } = parseYaml(content, filePath, settings.options || {});
54
55
  const relativePath = getRelative(filePath);
55
56
  return {
56
57
  content: parsedYaml,
57
58
  fileName: relativePath,
58
59
  fileFormat,
59
- dataFormat,
60
60
  };
61
61
  });
62
62
  allFiles.push(...yamlFiles);
@@ -73,7 +73,6 @@ export async function translateFiles(filePaths, placeholderPaths, transformPaths
73
73
  content: sanitizedContent,
74
74
  fileName: relativePath,
75
75
  fileFormat: fileType.toUpperCase(),
76
- dataFormat,
77
76
  };
78
77
  });
79
78
  allFiles.push(...files);
@@ -81,117 +80,6 @@ export async function translateFiles(filePaths, placeholderPaths, transformPaths
81
80
  }
82
81
  if (allFiles.length === 0) {
83
82
  logError('No files to translate were found. Please check your configuration and try again.');
84
- return;
85
- }
86
- if (options.dryRun) {
87
- const fileNames = allFiles.map((file) => `- ${file.fileName}`).join('\n');
88
- logSuccess(`Dry run: No files were sent to General Translation. Found files:\n${fileNames}`);
89
- return;
90
- }
91
- // Validate required settings are present
92
- if (!options.locales) {
93
- logErrorAndExit(noLocalesError);
94
- }
95
- if (!options.defaultLocale) {
96
- logErrorAndExit(noDefaultLocaleError);
97
- }
98
- if (!options.apiKey) {
99
- logErrorAndExit(noApiKeyError);
100
- }
101
- if (options.apiKey.startsWith('gtx-dev-')) {
102
- logErrorAndExit(devApiKeyError);
103
- }
104
- if (!options.projectId) {
105
- logErrorAndExit(noProjectIdError);
106
- }
107
- try {
108
- // Send all files in a single API call
109
- const response = await sendFiles(allFiles, {
110
- ...options,
111
- publish: false,
112
- wait: true,
113
- });
114
- const { data, locales, translations } = response;
115
- // Create file mapping for all file types
116
- const fileMapping = createFileMapping(filePaths, placeholderPaths, transformPaths, locales, options.defaultLocale);
117
- // Process any translations that were already completed and returned with the initial response
118
- const downloadStatus = await processInitialTranslations(translations, fileMapping, options);
119
- // Check for remaining translations
120
- await checkFileTranslations(data, locales, 600, (sourcePath, locale) => fileMapping[locale][sourcePath], downloadStatus, // Pass the already downloaded files to avoid duplicate requests
121
- options);
122
- }
123
- catch (error) {
124
- logErrorAndExit(`Error translating files: ${error}`);
125
- }
126
- }
127
- /**
128
- * Processes translations that were already completed and returned with the initial API response
129
- * @returns Set of downloaded file+locale combinations
130
- */
131
- async function processInitialTranslations(translations = [], fileMapping, options) {
132
- const downloadStatus = {
133
- downloaded: new Set(),
134
- failed: new Set(),
135
- };
136
- if (!translations || translations.length === 0) {
137
- return downloadStatus;
138
- }
139
- // Filter for ready translations
140
- const readyTranslations = translations.filter((translation) => translation.isReady && translation.fileName);
141
- if (readyTranslations.length > 0) {
142
- const spinner = createSpinner('dots');
143
- spinner.start('Downloading translations...');
144
- // Prepare batch download data
145
- const batchFiles = readyTranslations
146
- .map((translation) => {
147
- const { locale, fileName, id } = translation;
148
- const outputPath = fileMapping[locale][fileName];
149
- if (!outputPath) {
150
- return null;
151
- }
152
- return {
153
- translationId: id,
154
- inputPath: fileName,
155
- outputPath,
156
- fileLocale: `${fileName}:${locale}`,
157
- locale,
158
- };
159
- })
160
- .filter(Boolean);
161
- if (batchFiles.length === 0 || batchFiles[0] === null) {
162
- return downloadStatus;
163
- }
164
- // Use batch download if there are multiple files
165
- if (batchFiles.length > 1) {
166
- const batchResult = await downloadFileBatch(batchFiles.map(({ translationId, outputPath, inputPath, locale }) => ({
167
- translationId,
168
- outputPath,
169
- inputPath,
170
- locale,
171
- })), options);
172
- // Process results
173
- batchFiles.forEach((file) => {
174
- const { translationId, fileLocale } = file;
175
- if (batchResult.successful.includes(translationId)) {
176
- downloadStatus.downloaded.add(fileLocale);
177
- }
178
- else if (batchResult.failed.includes(translationId)) {
179
- downloadStatus.failed.add(fileLocale);
180
- }
181
- });
182
- }
183
- else if (batchFiles.length === 1) {
184
- // For a single file, use the original downloadFile method
185
- const file = batchFiles[0];
186
- const result = await downloadFile(file.translationId, file.outputPath, file.inputPath, file.locale, options);
187
- if (result) {
188
- downloadStatus.downloaded.add(file.fileLocale);
189
- }
190
- else {
191
- downloadStatus.failed.add(file.fileLocale);
192
- }
193
- }
194
- spinner.stop(chalk.green('Downloaded cached translations'));
195
83
  }
196
- return downloadStatus;
84
+ return allFiles;
197
85
  }