gtx-cli 1.2.3 → 1.2.4

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.
@@ -46,7 +46,7 @@ function sendFiles(files, options) {
46
46
  formData.append('targetLocales', JSON.stringify(options.locales));
47
47
  formData.append('projectId', options.projectId);
48
48
  formData.append('publish', String(options.publish));
49
- formData.append('versionId', options.versionId || '');
49
+ formData.append('versionId', options._versionId || '');
50
50
  formData.append('description', options.description || '');
51
51
  const response = yield fetch(`${options.baseUrl}/v1/project/translations/files/upload`, {
52
52
  method: 'POST',
@@ -1,11 +1,10 @@
1
1
  import { Settings, SupportedLibraries, Updates } from '../types';
2
2
  import { DataFormat } from '../types/data';
3
3
  type ApiOptions = Settings & {
4
- publish: boolean;
5
- wait: boolean;
6
4
  timeout: string;
7
5
  dataFormat: DataFormat;
8
6
  description?: string;
7
+ requireApproval?: boolean;
9
8
  };
10
9
  /**
11
10
  * Sends updates to the API
@@ -14,6 +13,7 @@ type ApiOptions = Settings & {
14
13
  * @returns The versionId of the updated project
15
14
  */
16
15
  export declare function sendUpdates(updates: Updates, options: ApiOptions, library: SupportedLibraries): Promise<{
17
- versionId: any;
18
- } | undefined>;
16
+ versionId: string;
17
+ locales: string[];
18
+ }>;
19
19
  export {};
@@ -16,7 +16,6 @@ exports.sendUpdates = sendUpdates;
16
16
  const chalk_1 = __importDefault(require("chalk"));
17
17
  const console_1 = require("../console");
18
18
  const updateConfig_1 = __importDefault(require("../fs/config/updateConfig"));
19
- const waitForUpdates_1 = require("./waitForUpdates");
20
19
  /**
21
20
  * Sends updates to the API
22
21
  * @param updates - The updates to send
@@ -30,11 +29,12 @@ function sendUpdates(updates, options, library) {
30
29
  // If additionalLocales is provided, additionalLocales + project.current_locales will be translated
31
30
  // If not, then options.locales will be translated
32
31
  // If neither, then project.current_locales will be translated
33
- const body = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ updates }, (options.locales && { locales: options.locales })), { metadata: globalMetadata, publish: options.publish }), (dataFormat && { dataFormat })), (options.versionId && { versionId: options.versionId })), (options.description && { description: options.description }));
32
+ const body = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ updates }, (options.locales && { locales: options.locales })), { metadata: globalMetadata }), (dataFormat && { dataFormat })), (options.version && { versionId: options.version })), (options.description && { description: options.description })), (options.requireApproval && {
33
+ requireApproval: options.requireApproval,
34
+ }));
34
35
  const spinner = (0, console_1.createSpinner)('dots');
35
36
  spinner.start(`Sending ${library} updates to General Translation API...`);
36
37
  try {
37
- const startTime = Date.now();
38
38
  const response = yield fetch(`${options.baseUrl}/v1/project/translations/update`, {
39
39
  method: 'POST',
40
40
  headers: Object.assign({ 'Content-Type': 'application/json' }, (apiKey && { 'x-gt-api-key': apiKey })),
@@ -44,27 +44,17 @@ function sendUpdates(updates, options, library) {
44
44
  spinner.stop(chalk_1.default.red(yield response.text()));
45
45
  process.exit(1);
46
46
  }
47
- if (response.status === 204) {
48
- spinner.stop(chalk_1.default.green('Sent updates'));
49
- (0, console_1.logSuccess)(yield response.text());
50
- return;
51
- }
52
47
  const { versionId, message, locales } = yield response.json();
53
48
  spinner.stop(chalk_1.default.green('Sent updates'));
54
49
  (0, console_1.logSuccess)(message);
55
- if (options.config)
50
+ if (options.config) {
56
51
  yield (0, updateConfig_1.default)({
57
52
  configFilepath: options.config,
58
53
  _versionId: versionId,
59
54
  locales,
60
55
  });
61
- // Wait for translations if wait is true
62
- if (options.wait && locales) {
63
- // timeout was validated earlier
64
- const timeout = parseInt(options.timeout) * 1000;
65
- yield (0, waitForUpdates_1.waitForUpdates)(apiKey, options.baseUrl, versionId, locales, startTime, timeout);
66
56
  }
67
- return { versionId };
57
+ return { versionId, locales };
68
58
  }
69
59
  catch (error) {
70
60
  spinner.stop(chalk_1.default.red('Failed to send updates'));
@@ -8,4 +8,4 @@
8
8
  * @param timeoutDuration - The timeout duration for the wait
9
9
  * @returns True if all translations are deployed, false otherwise
10
10
  */
11
- export declare const waitForUpdates: (apiKey: string, baseUrl: string, versionId: string, locales: string[], startTime: number, timeoutDuration: number) => Promise<boolean>;
11
+ export declare const waitForUpdates: (apiKey: string, baseUrl: string, versionId: string, startTime: number, timeoutDuration: number) => Promise<boolean>;
@@ -26,11 +26,10 @@ const generaltranslation_1 = require("generaltranslation");
26
26
  * @param timeoutDuration - The timeout duration for the wait
27
27
  * @returns True if all translations are deployed, false otherwise
28
28
  */
29
- const waitForUpdates = (apiKey, baseUrl, versionId, locales, startTime, timeoutDuration) => __awaiter(void 0, void 0, void 0, function* () {
29
+ const waitForUpdates = (apiKey, baseUrl, versionId, startTime, timeoutDuration) => __awaiter(void 0, void 0, void 0, function* () {
30
30
  console.log();
31
31
  const spinner = yield (0, console_1.createOraSpinner)();
32
32
  spinner.start('Waiting for translation...');
33
- const availableLocales = [];
34
33
  const checkDeployment = () => __awaiter(void 0, void 0, void 0, function* () {
35
34
  try {
36
35
  const response = yield fetch(`${baseUrl}/v1/project/translations/status/${encodeURIComponent(versionId)}`, {
@@ -39,10 +38,14 @@ const waitForUpdates = (apiKey, baseUrl, versionId, locales, startTime, timeoutD
39
38
  });
40
39
  if (response.ok) {
41
40
  const data = yield response.json();
42
- if (data.availableLocales) {
43
- data.availableLocales.forEach((locale) => {
44
- if (!availableLocales.includes(locale) &&
45
- locales.includes(locale)) {
41
+ const { availableLocales, locales, localesWaitingForApproval } = data;
42
+ if (localesWaitingForApproval.length > 0) {
43
+ spinner.text = `Waiting for approval for ${localesWaitingForApproval.length} locales`;
44
+ return false;
45
+ }
46
+ if (availableLocales) {
47
+ availableLocales.forEach((locale) => {
48
+ if (!availableLocales.includes(locale)) {
46
49
  availableLocales.push(locale);
47
50
  }
48
51
  });
@@ -54,7 +57,6 @@ const waitForUpdates = (apiKey, baseUrl, versionId, locales, startTime, timeoutD
54
57
  return `Translation completed for ${chalk_1.default.green(localeProperties.name)} (${chalk_1.default.green(localeProperties.code)})`;
55
58
  }),
56
59
  ];
57
- // The new clack spinner doesn't have suffixText, just update the message
58
60
  spinner.text = newSuffixText.join('\n');
59
61
  }
60
62
  if (locales.every((locale) => availableLocales.includes(locale))) {
@@ -1,4 +1,13 @@
1
- import { Settings, SupportedLibraries, SetupOptions } from '../types';
1
+ import { FilesOptions, Settings, SupportedLibraries, SetupOptions } from '../types';
2
+ export type TranslateOptions = {
3
+ config?: string;
4
+ defaultLocale?: string;
5
+ locales?: string[];
6
+ files?: FilesOptions;
7
+ apiKey?: string;
8
+ projectId?: string;
9
+ dryRun: boolean;
10
+ };
2
11
  export declare class BaseCLI {
3
12
  protected library: SupportedLibraries;
4
13
  protected additionalModules: SupportedLibraries[];
@@ -10,7 +19,7 @@ export declare class BaseCLI {
10
19
  protected setupInitCommand(): void;
11
20
  protected setupConfigureCommand(): void;
12
21
  protected setupSetupCommand(): void;
13
- protected handleGenericTranslate(settings: Settings): Promise<void>;
22
+ protected handleGenericTranslate(settings: Settings & TranslateOptions): Promise<void>;
14
23
  protected handleSetupReactCommand(options: SetupOptions): Promise<void>;
15
24
  protected handleInitCommand(ranReactSetup: boolean): Promise<void>;
16
25
  protected handleLoginCommand(): Promise<void>;
package/dist/cli/base.js CHANGED
@@ -91,10 +91,12 @@ class BaseCLI {
91
91
  .option('--project-id <id>', 'Project ID for the translation service', (0, utils_1.resolveProjectId)())
92
92
  .option('--default-language, --default-locale <locale>', 'Default locale (e.g., en)')
93
93
  .option('--new, --locales <locales...>', 'Space-separated list of locales (e.g., en fr es)')
94
- .action((options) => __awaiter(this, void 0, void 0, function* () {
94
+ .option('--dry-run', 'Dry run, does not send updates to General Translation API', false)
95
+ .action((initOptions) => __awaiter(this, void 0, void 0, function* () {
95
96
  (0, console_1.displayHeader)('Starting translation...');
96
- const settings = yield (0, generateSettings_1.generateSettings)(options);
97
- yield this.handleGenericTranslate(settings);
97
+ const settings = yield (0, generateSettings_1.generateSettings)(initOptions);
98
+ const options = Object.assign(Object.assign({}, initOptions), settings);
99
+ yield this.handleGenericTranslate(options);
98
100
  (0, console_1.endCommand)('Done!');
99
101
  }));
100
102
  }
@@ -103,7 +105,7 @@ class BaseCLI {
103
105
  .command('auth')
104
106
  .description('Generate a General Translation API key and project ID')
105
107
  .option('-c, --config <path>', 'Filepath to config file, by default gt.config.json', (0, findFilepath_1.default)(['gt.config.json']))
106
- .action((options) => __awaiter(this, void 0, void 0, function* () {
108
+ .action(() => __awaiter(this, void 0, void 0, function* () {
107
109
  (0, console_1.displayHeader)('Authenticating with General Translation...');
108
110
  yield this.handleLoginCommand();
109
111
  (0, console_1.endCommand)('Done!');
@@ -168,22 +170,6 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
168
170
  }
169
171
  handleGenericTranslate(settings) {
170
172
  return __awaiter(this, void 0, void 0, function* () {
171
- // Validate required settings are present
172
- if (!settings.locales) {
173
- (0, console_1.logErrorAndExit)(console_1.noLocalesError);
174
- }
175
- if (!settings.defaultLocale) {
176
- (0, console_1.logErrorAndExit)(console_1.noDefaultLocaleError);
177
- }
178
- if (!settings.files) {
179
- (0, console_1.logErrorAndExit)(console_1.noFilesError);
180
- }
181
- if (!settings.apiKey) {
182
- (0, console_1.logErrorAndExit)(console_1.noApiKeyError);
183
- }
184
- if (!settings.projectId) {
185
- (0, console_1.logErrorAndExit)(console_1.noProjectIdError);
186
- }
187
173
  // dataFormat for JSONs
188
174
  let dataFormat;
189
175
  if (this.library === 'next-intl') {
@@ -200,6 +186,9 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
200
186
  else {
201
187
  dataFormat = 'JSX';
202
188
  }
189
+ if (!settings.files) {
190
+ return;
191
+ }
203
192
  const { resolvedPaths: sourceFiles, placeholderPaths, transformPaths, } = settings.files;
204
193
  // Process all file types at once with a single call
205
194
  yield (0, translate_1.translateFiles)(sourceFiles, placeholderPaths, transformPaths, dataFormat, settings);
@@ -1,14 +1,10 @@
1
- import { WrapOptions, Options, Updates, SupportedFrameworks, SupportedLibraries } from '../types';
1
+ import { WrapOptions, SupportedFrameworks, SupportedLibraries } from '../types';
2
2
  import { ReactCLI } from './react';
3
3
  export declare class NextCLI extends ReactCLI {
4
- constructor(library: SupportedLibraries, additionalModules?: SupportedLibraries[]);
4
+ constructor(library: 'gt-next', additionalModules?: SupportedLibraries[]);
5
5
  init(): void;
6
6
  execute(): void;
7
- protected scanForContent(options: WrapOptions, framework: SupportedFrameworks, errors: string[], warnings: string[]): Promise<{
7
+ protected wrapContent(options: WrapOptions, framework: SupportedFrameworks, errors: string[], warnings: string[]): Promise<{
8
8
  filesUpdated: string[];
9
9
  }>;
10
- protected createInlineUpdates(options: Options): Promise<{
11
- updates: Updates;
12
- errors: string[];
13
- }>;
14
10
  }
package/dist/cli/next.js CHANGED
@@ -4,15 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.NextCLI = void 0;
7
- const createInlineUpdates_1 = __importDefault(require("../react/parse/createInlineUpdates"));
8
7
  const react_1 = require("./react");
9
- const scanForContent_1 = __importDefault(require("../next/parse/scanForContent"));
8
+ const wrapContent_1 = __importDefault(require("../next/parse/wrapContent"));
10
9
  const pkg = 'gt-next';
11
10
  class NextCLI extends react_1.ReactCLI {
12
11
  constructor(library, additionalModules) {
13
12
  super(library, additionalModules);
14
13
  }
15
14
  init() {
15
+ this.setupStageCommand();
16
16
  this.setupTranslateCommand();
17
17
  this.setupScanCommand();
18
18
  this.setupGenerateSourceCommand();
@@ -20,11 +20,8 @@ class NextCLI extends react_1.ReactCLI {
20
20
  execute() {
21
21
  super.execute();
22
22
  }
23
- scanForContent(options, framework, errors, warnings) {
24
- return (0, scanForContent_1.default)(options, pkg, errors, warnings);
25
- }
26
- createInlineUpdates(options) {
27
- return (0, createInlineUpdates_1.default)(options, pkg);
23
+ wrapContent(options, framework, errors, warnings) {
24
+ return (0, wrapContent_1.default)(options, pkg, errors, warnings);
28
25
  }
29
26
  }
30
27
  exports.NextCLI = NextCLI;
@@ -1,25 +1,18 @@
1
- import { Options, SupportedFrameworks, Updates, WrapOptions, GenerateSourceOptions, SupportedLibraries } from '../types';
1
+ import { Options, SupportedFrameworks, WrapOptions, GenerateSourceOptions, SupportedLibraries } from '../types';
2
2
  import { BaseCLI } from './base';
3
3
  export declare class ReactCLI extends BaseCLI {
4
- constructor(library: SupportedLibraries, additionalModules?: SupportedLibraries[]);
4
+ constructor(library: 'gt-react' | 'gt-next', additionalModules?: SupportedLibraries[]);
5
5
  init(): void;
6
6
  execute(): void;
7
- protected scanForContent(options: WrapOptions, framework: SupportedFrameworks, errors: string[], warnings: string[]): Promise<{
7
+ protected wrapContent(options: WrapOptions, framework: SupportedFrameworks, errors: string[], warnings: string[]): Promise<{
8
8
  filesUpdated: string[];
9
9
  }>;
10
- protected createDictionaryUpdates(options: Options, dictionaryPath: string, esbuildConfig?: any): Promise<Updates>;
11
- protected createInlineUpdates(options: Options): Promise<{
12
- updates: Updates;
13
- errors: string[];
14
- }>;
10
+ protected setupStageCommand(): void;
15
11
  protected setupTranslateCommand(): void;
16
12
  protected setupGenerateSourceCommand(): void;
17
13
  protected setupScanCommand(): void;
18
14
  protected handleGenerateSourceCommand(initOptions: GenerateSourceOptions): Promise<void>;
19
15
  protected handleScanCommand(options: WrapOptions): Promise<void>;
20
- protected handleTranslateCommand(initOptions: Options): Promise<void>;
21
- protected createUpdates(options: Options | GenerateSourceOptions, sourceDictionary: string | undefined): Promise<{
22
- updates: Updates;
23
- errors: string[];
24
- }>;
16
+ protected handleStage(initOptions: Options): Promise<void>;
17
+ protected handleTranslate(initOptions: Options): Promise<void>;
25
18
  }
package/dist/cli/react.js CHANGED
@@ -51,22 +51,19 @@ const commander_1 = require("commander");
51
51
  const console_1 = require("../console/console");
52
52
  const loadJSON_1 = __importDefault(require("../fs/loadJSON"));
53
53
  const findFilepath_1 = __importStar(require("../fs/findFilepath"));
54
- const createESBuildConfig_1 = __importDefault(require("../react/config/createESBuildConfig"));
55
54
  const chalk_1 = __importDefault(require("chalk"));
56
55
  const postProcess_1 = require("../hooks/postProcess");
57
- const fetchTranslations_1 = require("../api/fetchTranslations");
58
56
  const base_1 = require("./base");
59
- const scanForContent_1 = __importDefault(require("../react/parse/scanForContent"));
60
- const createDictionaryUpdates_1 = __importDefault(require("../react/parse/createDictionaryUpdates"));
61
- const createInlineUpdates_1 = __importDefault(require("../react/parse/createInlineUpdates"));
57
+ const wrapContent_1 = __importDefault(require("../react/parse/wrapContent"));
62
58
  const utils_1 = require("../fs/utils");
63
- const sendUpdates_1 = require("../api/sendUpdates");
64
- const save_1 = require("../formats/gt/save");
65
59
  const generateSettings_1 = require("../config/generateSettings");
66
60
  const saveJSON_1 = require("../fs/saveJSON");
67
61
  const parseFilesConfig_1 = require("../fs/config/parseFilesConfig");
68
- const node_fs_1 = __importDefault(require("node:fs"));
69
62
  const errors_1 = require("../console/errors");
63
+ const stage_1 = require("../translation/stage");
64
+ const parse_1 = require("../translation/parse");
65
+ const translate_1 = require("../translation/translate");
66
+ const updateConfig_1 = __importDefault(require("../fs/config/updateConfig"));
70
67
  const DEFAULT_TIMEOUT = 600;
71
68
  const pkg = 'gt-react';
72
69
  class ReactCLI extends base_1.BaseCLI {
@@ -74,6 +71,7 @@ class ReactCLI extends base_1.BaseCLI {
74
71
  super(library, additionalModules);
75
72
  }
76
73
  init() {
74
+ this.setupStageCommand();
77
75
  this.setupTranslateCommand();
78
76
  this.setupScanCommand();
79
77
  this.setupGenerateSourceCommand();
@@ -81,19 +79,36 @@ class ReactCLI extends base_1.BaseCLI {
81
79
  execute() {
82
80
  super.execute();
83
81
  }
84
- scanForContent(options, framework, errors, warnings) {
85
- return (0, scanForContent_1.default)(options, pkg, framework, errors, warnings);
82
+ wrapContent(options, framework, errors, warnings) {
83
+ return (0, wrapContent_1.default)(options, pkg, framework, errors, warnings);
86
84
  }
87
- createDictionaryUpdates(options, dictionaryPath, esbuildConfig) {
88
- return (0, createDictionaryUpdates_1.default)(options, dictionaryPath, esbuildConfig);
89
- }
90
- createInlineUpdates(options) {
91
- return (0, createInlineUpdates_1.default)(options, pkg);
85
+ setupStageCommand() {
86
+ commander_1.program
87
+ .command('stage')
88
+ .description('Submits the project to the General Translation API for translation. Translations created using this command will require human approval.')
89
+ .option('--config <path>', 'Filepath to config file, by default gt.config.json', (0, findFilepath_1.default)(['gt.config.json']))
90
+ .option('--api-key <key>', 'API key for General Translation cloud service')
91
+ .option('--project-id <id>', 'Project ID for the translation service', (0, utils_1.resolveProjectId)())
92
+ .option('--version-id <id>', 'Version ID for the translation service')
93
+ .option('--tsconfig, --jsconfig <path>', 'Path to jsconfig or tsconfig file', (0, findFilepath_1.default)(['./tsconfig.json', './jsconfig.json']))
94
+ .option('--dictionary <path>', 'Path to dictionary file')
95
+ .option('--src <paths...>', "Filepath to directory containing the app's source code, by default ./src || ./app || ./pages || ./components")
96
+ .option('--default-language, --default-locale <locale>', 'Default locale (e.g., en)')
97
+ .option('--new, --locales <locales...>', 'Space-separated list of locales (e.g., en fr es)')
98
+ .option('--inline', 'Include inline <T> tags in addition to dictionary file', true)
99
+ .option('--ignore-errors', 'Ignore errors encountered while scanning for <T> tags', false)
100
+ .option('--dry-run', 'Dry run, does not send updates to General Translation API', false)
101
+ .option('--timeout <seconds>', 'Timeout in seconds for waiting for updates to be deployed to the CDN', DEFAULT_TIMEOUT.toString())
102
+ .action((options) => __awaiter(this, void 0, void 0, function* () {
103
+ (0, console_1.displayHeader)('Staging project for translation with approval...');
104
+ yield this.handleStage(options);
105
+ (0, console_1.endCommand)('Done!');
106
+ }));
92
107
  }
93
108
  setupTranslateCommand() {
94
109
  commander_1.program
95
110
  .command('translate')
96
- .description('Scans the project for a dictionary and/or <T> tags, and updates the General Translation remote dictionary with the latest content.')
111
+ .description('Scans the project for a dictionary and/or <T> tags, and sends the updates to the General Translation API for translation.')
97
112
  .option('--config <path>', 'Filepath to config file, by default gt.config.json', (0, findFilepath_1.default)(['gt.config.json']))
98
113
  .option('--api-key <key>', 'API key for General Translation cloud service')
99
114
  .option('--project-id <id>', 'Project ID for the translation service', (0, utils_1.resolveProjectId)())
@@ -106,15 +121,10 @@ class ReactCLI extends base_1.BaseCLI {
106
121
  .option('--inline', 'Include inline <T> tags in addition to dictionary file', true)
107
122
  .option('--ignore-errors', 'Ignore errors encountered while scanning for <T> tags', false)
108
123
  .option('--dry-run', 'Dry run, does not send updates to General Translation API', false)
109
- .option('--no-wait', 'Do not wait for the updates to be deployed to the CDN before exiting', true // Default value of options.wait
110
- )
111
- .option('--publish', 'Publish updates to the CDN.', false // Default value of options.publish
112
- )
113
- .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.')
114
124
  .option('--timeout <seconds>', 'Timeout in seconds for waiting for updates to be deployed to the CDN', DEFAULT_TIMEOUT.toString())
115
125
  .action((options) => __awaiter(this, void 0, void 0, function* () {
116
126
  (0, console_1.displayHeader)('Translating project...');
117
- yield this.handleTranslateCommand(options);
127
+ yield this.handleTranslate(options);
118
128
  (0, console_1.endCommand)('Done!');
119
129
  }));
120
130
  }
@@ -165,7 +175,7 @@ class ReactCLI extends base_1.BaseCLI {
165
175
  }
166
176
  // User has to provide a dictionary file
167
177
  // will not read from settings.files.resolvedPaths.json
168
- const { updates, errors } = yield this.createUpdates(options, options.dictionary);
178
+ const { updates, errors } = yield (0, parse_1.createUpdates)(options, options.dictionary, this.library === 'gt-next' ? 'gt-next' : 'gt-react');
169
179
  if (errors.length > 0) {
170
180
  if (options.ignoreErrors) {
171
181
  (0, console_1.logWarning)(chalk_1.default.red(`CLI tool encountered errors while scanning for ${chalk_1.default.green('<T>')} tags. These components will not be translated.\n` +
@@ -190,10 +200,9 @@ class ReactCLI extends base_1.BaseCLI {
190
200
  newData[hash] = source;
191
201
  }
192
202
  }
193
- const { placeholderPaths } = settings.files;
194
203
  // Save source file if files.json is provided
195
- if (placeholderPaths.gt) {
196
- const translationFiles = (0, parseFilesConfig_1.resolveLocaleFiles)(placeholderPaths, settings.defaultLocale);
204
+ if (settings.files && settings.files.placeholderPaths.gt) {
205
+ const translationFiles = (0, parseFilesConfig_1.resolveLocaleFiles)(settings.files.placeholderPaths, settings.defaultLocale);
197
206
  if (!translationFiles.gt) {
198
207
  (0, console_1.logError)(errors_1.noFilesError);
199
208
  process.exit(1);
@@ -202,7 +211,7 @@ class ReactCLI extends base_1.BaseCLI {
202
211
  (0, console_1.logStep)('Source file saved successfully!');
203
212
  // Also save translations (after merging with existing translations)
204
213
  for (const locale of settings.locales) {
205
- const translationsFile = (0, parseFilesConfig_1.resolveLocaleFiles)(placeholderPaths, locale);
214
+ const translationsFile = (0, parseFilesConfig_1.resolveLocaleFiles)(settings.files.placeholderPaths, locale);
206
215
  if (!translationsFile.gt) {
207
216
  continue;
208
217
  }
@@ -238,7 +247,7 @@ class ReactCLI extends base_1.BaseCLI {
238
247
  let errors = [];
239
248
  let warnings = [];
240
249
  // Wrap all JSX elements in the src directory with a <T> tag, with unique ids
241
- const { filesUpdated } = yield this.scanForContent(options, 'react', errors, warnings);
250
+ const { filesUpdated } = yield this.wrapContent(options, 'react', errors, warnings);
242
251
  if (errors.length > 0) {
243
252
  (0, console_1.logError)(chalk_1.default.red('Failed to write files:\n') + errors.join('\n'));
244
253
  }
@@ -259,156 +268,53 @@ class ReactCLI extends base_1.BaseCLI {
259
268
  }
260
269
  });
261
270
  }
262
- handleTranslateCommand(initOptions) {
271
+ handleStage(initOptions) {
272
+ return __awaiter(this, void 0, void 0, function* () {
273
+ const settings = yield (0, generateSettings_1.generateSettings)(initOptions);
274
+ // First run the base class's handleTranslate method
275
+ const options = Object.assign(Object.assign({}, initOptions), settings);
276
+ if (!settings.stageTranslations) {
277
+ // Update settings.stageTranslations to true
278
+ settings.stageTranslations = true;
279
+ yield (0, updateConfig_1.default)({
280
+ configFilepath: options.config,
281
+ stageTranslations: true,
282
+ });
283
+ }
284
+ const pkg = this.library === 'gt-next' ? 'gt-next' : 'gt-react';
285
+ yield (0, stage_1.stageProject)(options, pkg);
286
+ });
287
+ }
288
+ handleTranslate(initOptions) {
263
289
  const _super = Object.create(null, {
264
290
  handleGenericTranslate: { get: () => super.handleGenericTranslate }
265
291
  });
266
292
  return __awaiter(this, void 0, void 0, function* () {
267
- var _a, _b;
268
293
  const settings = yield (0, generateSettings_1.generateSettings)(initOptions);
269
294
  // First run the base class's handleTranslate method
270
295
  const options = Object.assign(Object.assign({}, initOptions), settings);
271
- if (!options.dryRun) {
272
- try {
273
- yield _super.handleGenericTranslate.call(this, settings);
274
- // If the base class's handleTranslate completes successfully, continue with ReactCLI-specific code
275
- }
276
- catch (error) {
277
- // Continue with ReactCLI-specific code even if base handleTranslate failed
278
- }
279
- }
280
- if (!options.dictionary) {
281
- options.dictionary = (0, findFilepath_1.default)([
282
- './dictionary.js',
283
- './src/dictionary.js',
284
- './dictionary.json',
285
- './src/dictionary.json',
286
- './dictionary.ts',
287
- './src/dictionary.ts',
288
- ]);
289
- }
290
- let sourceFile;
291
- // If options.dictionary is provided, use options.dictionary as the source file
292
- if (options.dictionary) {
293
- sourceFile = options.dictionary;
294
- }
295
- // Separate defaultLocale from locales
296
- options.locales = options.locales.filter((locale) => locale !== options.defaultLocale);
297
- // validate timeout
298
- const timeout = parseInt(options.timeout);
299
- if (isNaN(timeout) || timeout < 0) {
300
- (0, console_1.logErrorAndExit)(`Invalid timeout: ${options.timeout}. Must be a positive integer.`);
301
- }
302
- options.timeout = timeout.toString();
303
- // ---- CREATING UPDATES ---- //
304
- const { updates, errors } = yield this.createUpdates(options, sourceFile);
305
- if (errors.length > 0) {
306
- if (options.ignoreErrors) {
307
- (0, console_1.logWarning)(chalk_1.default.red(`CLI tool encountered errors while scanning for ${chalk_1.default.green('<T>')} tags. These components will not be translated.\n` +
308
- errors
309
- .map((error) => chalk_1.default.yellow('• Warning: ') + error + '\n')
310
- .join('')));
311
- }
312
- else {
313
- (0, console_1.logError)(chalk_1.default.red(`CLI tool encountered errors while scanning for ${chalk_1.default.green('<T>')} tags. ${chalk_1.default.gray('To ignore these errors, re-run with --ignore-errors')}\n` +
314
- errors
315
- .map((error) => chalk_1.default.red('• Error: ') + error + '\n')
316
- .join('')));
317
- }
296
+ try {
297
+ yield _super.handleGenericTranslate.call(this, options);
298
+ // If the base class's handleTranslate completes successfully, continue with ReactCLI-specific code
318
299
  }
319
- // If files.gt.output is not provided in the config, publish the translations
320
- if (!((_b = (_a = settings.files) === null || _a === void 0 ? void 0 : _a.resolvedPaths) === null || _b === void 0 ? void 0 : _b.gt)) {
321
- options.publish = true;
300
+ catch (error) {
301
+ // Continue with ReactCLI-specific code even if base handleTranslate failed
322
302
  }
323
- if (options.dryRun) {
324
- (0, console_1.logSuccess)('Dry run complete. No translations were sent to General Translation.');
325
- process.exit(0);
326
- }
327
- // Send updates to General Translation API
328
- if (updates.length) {
329
- // Error if no API key at this point
330
- if (!settings.apiKey) {
331
- (0, console_1.logError)(chalk_1.default.red('No General Translation API key found. Provide one via the --api-key flag or as the GT_API_KEY environment variable.'));
332
- process.exit(1);
333
- }
334
- // Error if no projectId at this point
335
- if (!settings.projectId) {
336
- (0, console_1.logError)(chalk_1.default.red('No General Translation Project ID found. Provide one via the --project-id flag, gt.config.json, or the GT_PROJECT_ID environment variable.'));
337
- process.exit(1);
338
- }
339
- const updateResponse = yield (0, sendUpdates_1.sendUpdates)(updates, Object.assign(Object.assign({}, settings), { publish: options.publish, wait: options.wait, timeout: options.timeout, dataFormat: 'JSX' }), this.library);
340
- const versionId = updateResponse === null || updateResponse === void 0 ? void 0 : updateResponse.versionId;
341
- // Save translations to local directory if files.gt.output is provided
342
- if (versionId && options.files.placeholderPaths.gt) {
343
- const translations = yield (0, fetchTranslations_1.fetchTranslations)(settings.baseUrl, settings.apiKey, versionId);
344
- yield (0, save_1.saveTranslations)(translations, options.files.placeholderPaths, 'JSX');
303
+ if (!settings.stageTranslations) {
304
+ // If stageTranslations is false, stage the project
305
+ const pkg = this.library === 'gt-next' ? 'gt-next' : 'gt-react';
306
+ const results = yield (0, stage_1.stageProject)(options, pkg);
307
+ if (results) {
308
+ yield (0, translate_1.translate)(options, results.versionId);
345
309
  }
346
310
  }
347
311
  else {
348
- (0, console_1.logError)(chalk_1.default.red(`No in-line content or dictionaries were found for ${chalk_1.default.green(this.library)}. Are you sure you're running this command in the right directory?`));
349
- }
350
- });
351
- }
352
- createUpdates(options, sourceDictionary) {
353
- return __awaiter(this, void 0, void 0, function* () {
354
- let updates = [];
355
- let errors = [];
356
- // Parse dictionary with esbuildConfig
357
- if (sourceDictionary &&
358
- node_fs_1.default.existsSync(sourceDictionary) &&
359
- node_fs_1.default.statSync(sourceDictionary).isFile()) {
360
- if (sourceDictionary.endsWith('.json')) {
361
- updates = [
362
- ...updates,
363
- ...(yield this.createDictionaryUpdates(options, sourceDictionary)),
364
- ];
365
- }
366
- else {
367
- let esbuildConfig;
368
- if (options.jsconfig) {
369
- const jsconfig = (0, loadJSON_1.default)(options.jsconfig);
370
- if (!jsconfig) {
371
- (0, console_1.logError)(`Failed to resolve jsconfig.json or tsconfig.json at provided filepath: "${options.jsconfig}"`);
372
- process.exit(1);
373
- }
374
- esbuildConfig = (0, createESBuildConfig_1.default)(jsconfig);
375
- }
376
- else {
377
- esbuildConfig = (0, createESBuildConfig_1.default)({});
378
- }
379
- updates = [
380
- ...updates,
381
- ...(yield this.createDictionaryUpdates(options, sourceDictionary, esbuildConfig)),
382
- ];
312
+ if (!settings._versionId) {
313
+ (0, console_1.logError)(errors_1.noVersionIdError);
314
+ process.exit(1);
383
315
  }
316
+ yield (0, translate_1.translate)(options, settings._versionId);
384
317
  }
385
- // Scan through project for <T> tags
386
- if (options.inline) {
387
- const { updates: newUpdates, errors: newErrors } = yield this.createInlineUpdates(options);
388
- errors = [...errors, ...newErrors];
389
- updates = [...updates, ...newUpdates];
390
- }
391
- // Metadata addition and validation
392
- const idHashMap = new Map();
393
- const duplicateIds = new Set();
394
- updates = updates.map((update) => {
395
- if (!update.metadata.id)
396
- return update;
397
- const existingHash = idHashMap.get(update.metadata.id);
398
- if (existingHash) {
399
- if (existingHash !== update.metadata.hash) {
400
- errors.push(`Hashes don't match on two components with the same id: ${chalk_1.default.blue(update.metadata.id)}. Check your ${chalk_1.default.green('<T>')} tags and dictionary entries and make sure you're not accidentally duplicating IDs.`);
401
- duplicateIds.add(update.metadata.id);
402
- }
403
- }
404
- else {
405
- idHashMap.set(update.metadata.id, update.metadata.hash);
406
- }
407
- return update;
408
- });
409
- // Filter out updates with duplicate IDs
410
- updates = updates.filter((update) => !duplicateIds.has(update.metadata.id));
411
- return { updates, errors };
412
318
  });
413
319
  }
414
320
  }
@@ -30,7 +30,7 @@ const constants_1 = require("../utils/constants");
30
30
  */
31
31
  function generateSettings(options) {
32
32
  return __awaiter(this, void 0, void 0, function* () {
33
- var _a;
33
+ var _a, _b;
34
34
  // Load config file
35
35
  let gtConfig = {};
36
36
  if (options.config && !options.config.endsWith('.json')) {
@@ -79,19 +79,24 @@ function generateSettings(options) {
79
79
  // Display projectId if present
80
80
  if (mergedOptions.projectId)
81
81
  (0, console_1.displayProjectId)(mergedOptions.projectId);
82
+ // Add stageTranslations if not provided
83
+ // For human review, always stage the project
84
+ mergedOptions.stageTranslations = (_a = mergedOptions.stageTranslations) !== null && _a !== void 0 ? _a : false;
82
85
  // Populate src if not provided
83
86
  mergedOptions.src =
84
87
  mergedOptions.src ||
85
88
  (0, findFilepath_1.findFilepaths)(['./src', './app', './pages', './components']);
86
89
  // Resolve all glob patterns in the files object
87
- mergedOptions.files = (0, parseFilesConfig_1.resolveFiles)(mergedOptions.files || {}, mergedOptions.defaultLocale);
90
+ mergedOptions.files = mergedOptions.files
91
+ ? (0, parseFilesConfig_1.resolveFiles)(mergedOptions.files, mergedOptions.defaultLocale)
92
+ : undefined;
88
93
  // if there's no existing config file, creates one
89
94
  // does not include the API key to avoid exposing it
90
95
  if (!node_fs_1.default.existsSync(mergedOptions.config)) {
91
96
  yield (0, setupConfig_1.default)(mergedOptions.config, {
92
97
  projectId: mergedOptions.projectId,
93
98
  defaultLocale: mergedOptions.defaultLocale,
94
- locales: ((_a = mergedOptions.locales) === null || _a === void 0 ? void 0 : _a.length) > 0 ? mergedOptions.locales : undefined,
99
+ locales: ((_b = mergedOptions.locales) === null || _b === void 0 ? void 0 : _b.length) > 0 ? mergedOptions.locales : undefined,
95
100
  });
96
101
  }
97
102
  (0, validateSettings_1.validateSettings)(mergedOptions);
@@ -14,3 +14,4 @@ export declare const noDataFormatError = "No data format found! Please make sure
14
14
  export declare const noSupportedDataFormatError = "Unsupported data format! Please make sure your translationsDir parameter ends with a supported file extension.";
15
15
  export declare const noApiKeyError = "No API key found! Please provide an API key using the --api-key flag or set the GT_API_KEY environment variable.";
16
16
  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.";
17
+ 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.";
@@ -14,10 +14,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.noProjectIdError = exports.noApiKeyError = exports.noSupportedDataFormatError = exports.noDataFormatError = exports.noSourceFileError = exports.noFilesError = exports.noDefaultLocaleError = exports.noLocalesError = exports.warnTernarySync = exports.warnTemplateLiteralSync = exports.warnNonStaticExpressionSync = exports.warnHasUnwrappedExpressionSync = exports.warnNoIdSync = exports.warnVariablePropSync = exports.warnApiKeyInConfigSync = void 0;
17
+ exports.noVersionIdError = exports.noProjectIdError = exports.noApiKeyError = exports.noSupportedDataFormatError = exports.noDataFormatError = exports.noSourceFileError = exports.noFilesError = exports.noDefaultLocaleError = exports.noLocalesError = exports.warnTernarySync = exports.warnTemplateLiteralSync = exports.warnNonStaticExpressionSync = exports.warnHasUnwrappedExpressionSync = exports.warnNoIdSync = exports.warnVariablePropSync = exports.warnApiKeyInConfigSync = void 0;
18
18
  // Export all logging functions
19
19
  __exportStar(require("./logging"), exports);
20
- // Synchronous wrappers for backward compatibility
20
+ // Synchronous wrappers for backward compatibility
21
21
  const warnApiKeyInConfigSync = (optionsFilepath) => `Found apiKey in "${optionsFilepath}". Your API key is exposed! Please remove it from the file and include it as an environment variable.`;
22
22
  exports.warnApiKeyInConfigSync = warnApiKeyInConfigSync;
23
23
  const warnVariablePropSync = (file, attrName, value) => `Found <T> component in ${file} with variable ${attrName}: "${value}". Change "${attrName}" to ensure this content is translated.`;
@@ -41,3 +41,4 @@ exports.noDataFormatError = `No data format found! Please make sure your transla
41
41
  exports.noSupportedDataFormatError = `Unsupported data format! Please make sure your translationsDir parameter ends with a supported file extension.`;
42
42
  exports.noApiKeyError = `No API key found! Please provide an API key using the --api-key flag or set the GT_API_KEY environment variable.`;
43
43
  exports.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.`;
44
+ exports.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.`;
@@ -1,5 +1,6 @@
1
1
  import { ResolvedFiles, Settings, TransformFiles } from '../../types';
2
2
  import { DataFormat } from '../../types/data';
3
+ import { TranslateOptions } from '../../cli/base';
3
4
  /**
4
5
  * Sends multiple files to the API for translation
5
6
  * @param filePaths - Resolved file paths for different file types
@@ -10,4 +11,4 @@ import { DataFormat } from '../../types/data';
10
11
  * @param options - Translation options including API settings
11
12
  * @returns Promise that resolves when translation is complete
12
13
  */
13
- export declare function translateFiles(filePaths: ResolvedFiles, placeholderPaths: ResolvedFiles, transformPaths: TransformFiles, dataFormat: DataFormat | undefined, options: Settings): Promise<void>;
14
+ export declare function translateFiles(filePaths: ResolvedFiles, placeholderPaths: ResolvedFiles, transformPaths: TransformFiles, dataFormat: DataFormat | undefined, options: Settings & TranslateOptions): Promise<void>;
@@ -77,9 +77,26 @@ function translateFiles(filePaths_1, placeholderPaths_1, transformPaths_1) {
77
77
  }
78
78
  }
79
79
  if (allFiles.length === 0) {
80
- (0, console_1.logError)('No files to translate');
80
+ (0, console_1.logError)('No files to translate were found. Please check your configuration and try again.');
81
81
  return;
82
82
  }
83
+ if (options.dryRun) {
84
+ (0, console_1.logSuccess)('Dry run: No files were sent to General Translation.');
85
+ return;
86
+ }
87
+ // Validate required settings are present
88
+ if (!options.locales) {
89
+ (0, console_1.logErrorAndExit)(console_1.noLocalesError);
90
+ }
91
+ if (!options.defaultLocale) {
92
+ (0, console_1.logErrorAndExit)(console_1.noDefaultLocaleError);
93
+ }
94
+ if (!options.apiKey) {
95
+ (0, console_1.logErrorAndExit)(console_1.noApiKeyError);
96
+ }
97
+ if (!options.projectId) {
98
+ (0, console_1.logErrorAndExit)(console_1.noProjectIdError);
99
+ }
83
100
  try {
84
101
  // Send all files in a single API call
85
102
  const response = yield (0, sendFiles_1.sendFiles)(allFiles, Object.assign(Object.assign({}, options), { publish: false, wait: true }));
@@ -2,9 +2,10 @@
2
2
  * Update the config file version id, locales, and projectId (if necessary)
3
3
  * @param {Record<string, any>} configObject - The config object to write if the file does not exist.
4
4
  */
5
- export default function updateConfig({ configFilepath, projectId, _versionId, locales, }: {
5
+ export default function updateConfig({ configFilepath, projectId, _versionId, locales, stageTranslations, }: {
6
6
  configFilepath: string;
7
7
  projectId?: string;
8
8
  _versionId?: string;
9
9
  locales?: string[];
10
+ stageTranslations?: boolean;
10
11
  }): Promise<void>;
@@ -20,9 +20,9 @@ const console_1 = require("../../console/console");
20
20
  * @param {Record<string, any>} configObject - The config object to write if the file does not exist.
21
21
  */
22
22
  function updateConfig(_a) {
23
- return __awaiter(this, arguments, void 0, function* ({ configFilepath, projectId, _versionId, locales, }) {
23
+ return __awaiter(this, arguments, void 0, function* ({ configFilepath, projectId, _versionId, locales, stageTranslations, }) {
24
24
  // Filter out empty string values from the config object
25
- const newContent = Object.assign(Object.assign({}, (projectId && { projectId })), (_versionId && { _versionId }));
25
+ const newContent = Object.assign(Object.assign(Object.assign({}, (projectId && { projectId })), (_versionId && { _versionId })), (stageTranslations && { stageTranslations }));
26
26
  try {
27
27
  // if file exists
28
28
  let oldContent = {};
@@ -6,6 +6,6 @@ import { WrapOptions } from '../../types';
6
6
  * @param options - The options object
7
7
  * @returns An object containing the updates and errors
8
8
  */
9
- export default function scanForContentNext(options: WrapOptions, pkg: 'gt-next', errors: string[], warnings: string[]): Promise<{
9
+ export default function wrapContentNext(options: WrapOptions, pkg: 'gt-next', errors: string[], warnings: string[]): Promise<{
10
10
  filesUpdated: string[];
11
11
  }>;
@@ -45,7 +45,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
45
  return (mod && mod.__esModule) ? mod : { "default": mod };
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.default = scanForContentNext;
48
+ exports.default = wrapContentNext;
49
49
  const node_fs_1 = __importDefault(require("node:fs"));
50
50
  const t = __importStar(require("@babel/types"));
51
51
  const parser_1 = require("@babel/parser");
@@ -72,7 +72,7 @@ const IMPORT_MAP = {
72
72
  * @param options - The options object
73
73
  * @returns An object containing the updates and errors
74
74
  */
75
- function scanForContentNext(options, pkg, errors, warnings) {
75
+ function wrapContentNext(options, pkg, errors, warnings) {
76
76
  return __awaiter(this, void 0, void 0, function* () {
77
77
  const srcDirectory = options.src || ['./'];
78
78
  const files = srcDirectory.flatMap((dir) => (0, findJsxFilepath_1.getFiles)(dir));
@@ -6,6 +6,6 @@ import { SupportedFrameworks, WrapOptions } from '../../types';
6
6
  * @param options - The options object
7
7
  * @returns An object containing the updates and errors
8
8
  */
9
- export default function scanForContentReact(options: WrapOptions, pkg: 'gt-react', framework: SupportedFrameworks, errors: string[], warnings: string[]): Promise<{
9
+ export default function wrapContentReact(options: WrapOptions, pkg: 'gt-react', framework: SupportedFrameworks, errors: string[], warnings: string[]): Promise<{
10
10
  filesUpdated: string[];
11
11
  }>;
@@ -45,7 +45,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
45
  return (mod && mod.__esModule) ? mod : { "default": mod };
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.default = scanForContentReact;
48
+ exports.default = wrapContentReact;
49
49
  const node_fs_1 = __importDefault(require("node:fs"));
50
50
  const node_path_1 = __importDefault(require("node:path"));
51
51
  const t = __importStar(require("@babel/types"));
@@ -72,7 +72,7 @@ const IMPORT_MAP = {
72
72
  * @param options - The options object
73
73
  * @returns An object containing the updates and errors
74
74
  */
75
- function scanForContentReact(options, pkg, framework, errors, warnings) {
75
+ function wrapContentReact(options, pkg, framework, errors, warnings) {
76
76
  return __awaiter(this, void 0, void 0, function* () {
77
77
  const srcDirectory = options.src || ['./'];
78
78
  const files = srcDirectory.flatMap((dir) => (0, findJsxFilepath_1.getFiles)(dir));
@@ -23,8 +23,8 @@ const generateSettings_1 = require("../config/generateSettings");
23
23
  const postProcess_2 = require("../hooks/postProcess");
24
24
  const handleInitGT_1 = __importDefault(require("../next/parse/handleInitGT"));
25
25
  const packageJson_1 = require("../utils/packageJson");
26
- const scanForContent_1 = __importDefault(require("../react/parse/scanForContent"));
27
- const scanForContent_2 = __importDefault(require("../next/parse/scanForContent"));
26
+ const wrapContent_1 = __importDefault(require("../react/parse/wrapContent"));
27
+ const wrapContent_2 = __importDefault(require("../next/parse/wrapContent"));
28
28
  const packageManager_1 = require("../utils/packageManager");
29
29
  const installPackage_1 = require("../utils/installPackage");
30
30
  function handleSetupReactCommand(options) {
@@ -111,7 +111,7 @@ Please let us know what you would like to see supported at https://github.com/ge
111
111
  const spinner = (0, console_1.createSpinner)();
112
112
  spinner.start('Wrapping JSX content with <T> tags...');
113
113
  // Wrap all JSX elements in the src directory with a <T> tag, with unique ids
114
- const { filesUpdated: filesUpdatedNext } = yield (0, scanForContent_2.default)(mergeOptions, 'gt-next', errors, warnings);
114
+ const { filesUpdated: filesUpdatedNext } = yield (0, wrapContent_2.default)(mergeOptions, 'gt-next', errors, warnings);
115
115
  filesUpdated = [...filesUpdated, ...filesUpdatedNext];
116
116
  spinner.stop(chalk_1.default.green(`Success! Added <T> tags and updated ${chalk_1.default.bold.cyan(filesUpdated.length)} files:\n`) + filesUpdated.map((file) => `${chalk_1.default.green('-')} ${file}`).join('\n'));
117
117
  if (addWithGTConfig) {
@@ -141,7 +141,7 @@ Please let us know what you would like to see supported at https://github.com/ge
141
141
  const spinner = (0, console_1.createSpinner)();
142
142
  spinner.start('Wrapping JSX content with <T> tags...');
143
143
  // Wrap all JSX elements in the src directory with a <T> tag, with unique ids
144
- const { filesUpdated: filesUpdatedReact } = yield (0, scanForContent_1.default)(mergeOptions, 'gt-react', frameworkType, errors, warnings);
144
+ const { filesUpdated: filesUpdatedReact } = yield (0, wrapContent_1.default)(mergeOptions, 'gt-react', frameworkType, errors, warnings);
145
145
  filesUpdated = [...filesUpdated, ...filesUpdatedReact];
146
146
  spinner.stop(chalk_1.default.green(`Success! Added <T> tags and updated ${chalk_1.default.bold.cyan(filesUpdated.length)} files:\n`) + filesUpdated.map((file) => `${chalk_1.default.green('-')} ${file}`).join('\n'));
147
147
  if (errors.length > 0) {
@@ -0,0 +1,14 @@
1
+ import { Options, GenerateSourceOptions, Updates } from '../types';
2
+ /**
3
+ * Searches for gt-react or gt-next dictionary files and creates updates for them,
4
+ * as well as inline updates for <T> tags and useGT()/getGT() calls
5
+ *
6
+ * @param options - The options object
7
+ * @param sourceDictionary - The source dictionary file path
8
+ * @param pkg - The package name
9
+ * @returns An object containing the updates and errors
10
+ */
11
+ export declare function createUpdates(options: Options | GenerateSourceOptions, sourceDictionary: string | undefined, pkg: 'gt-react' | 'gt-next'): Promise<{
12
+ updates: Updates;
13
+ errors: string[];
14
+ }>;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.createUpdates = createUpdates;
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const logging_1 = require("../console/logging");
18
+ const loadJSON_1 = __importDefault(require("../fs/loadJSON"));
19
+ const createDictionaryUpdates_1 = __importDefault(require("../react/parse/createDictionaryUpdates"));
20
+ const createInlineUpdates_1 = __importDefault(require("../react/parse/createInlineUpdates"));
21
+ const createESBuildConfig_1 = __importDefault(require("../react/config/createESBuildConfig"));
22
+ const chalk_1 = __importDefault(require("chalk"));
23
+ /**
24
+ * Searches for gt-react or gt-next dictionary files and creates updates for them,
25
+ * as well as inline updates for <T> tags and useGT()/getGT() calls
26
+ *
27
+ * @param options - The options object
28
+ * @param sourceDictionary - The source dictionary file path
29
+ * @param pkg - The package name
30
+ * @returns An object containing the updates and errors
31
+ */
32
+ function createUpdates(options, sourceDictionary, pkg) {
33
+ return __awaiter(this, void 0, void 0, function* () {
34
+ let updates = [];
35
+ let errors = [];
36
+ // Parse dictionary with esbuildConfig
37
+ if (sourceDictionary &&
38
+ fs_1.default.existsSync(sourceDictionary) &&
39
+ fs_1.default.statSync(sourceDictionary).isFile()) {
40
+ if (sourceDictionary.endsWith('.json')) {
41
+ updates = [
42
+ ...updates,
43
+ ...(yield (0, createDictionaryUpdates_1.default)(options, sourceDictionary)),
44
+ ];
45
+ }
46
+ else {
47
+ let esbuildConfig;
48
+ if (options.jsconfig) {
49
+ const jsconfig = (0, loadJSON_1.default)(options.jsconfig);
50
+ if (!jsconfig) {
51
+ (0, logging_1.logError)(`Failed to resolve jsconfig.json or tsconfig.json at provided filepath: "${options.jsconfig}"`);
52
+ process.exit(1);
53
+ }
54
+ esbuildConfig = (0, createESBuildConfig_1.default)(jsconfig);
55
+ }
56
+ else {
57
+ esbuildConfig = (0, createESBuildConfig_1.default)({});
58
+ }
59
+ updates = [
60
+ ...updates,
61
+ ...(yield (0, createDictionaryUpdates_1.default)(options, sourceDictionary, esbuildConfig)),
62
+ ];
63
+ }
64
+ }
65
+ // Scan through project for <T> tags
66
+ if (options.inline) {
67
+ const { updates: newUpdates, errors: newErrors } = yield (0, createInlineUpdates_1.default)(options, pkg);
68
+ errors = [...errors, ...newErrors];
69
+ updates = [...updates, ...newUpdates];
70
+ }
71
+ // Metadata addition and validation
72
+ const idHashMap = new Map();
73
+ const duplicateIds = new Set();
74
+ updates = updates.map((update) => {
75
+ if (!update.metadata.id)
76
+ return update;
77
+ const existingHash = idHashMap.get(update.metadata.id);
78
+ if (existingHash) {
79
+ if (existingHash !== update.metadata.hash) {
80
+ errors.push(`Hashes don't match on two components with the same id: ${chalk_1.default.blue(update.metadata.id)}. Check your ${chalk_1.default.green('<T>')} tags and dictionary entries and make sure you're not accidentally duplicating IDs.`);
81
+ duplicateIds.add(update.metadata.id);
82
+ }
83
+ }
84
+ else {
85
+ idHashMap.set(update.metadata.id, update.metadata.hash);
86
+ }
87
+ return update;
88
+ });
89
+ // Filter out updates with duplicate IDs
90
+ updates = updates.filter((update) => !duplicateIds.has(update.metadata.id));
91
+ return { updates, errors };
92
+ });
93
+ }
@@ -0,0 +1,5 @@
1
+ import { Options, Settings } from '../types';
2
+ export declare function stageProject(settings: Options & Settings, pkg: 'gt-react' | 'gt-next'): Promise<{
3
+ versionId: string;
4
+ locales: string[];
5
+ } | null>;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.stageProject = stageProject;
16
+ const errors_1 = require("../console/errors");
17
+ const chalk_1 = __importDefault(require("chalk"));
18
+ const findFilepath_1 = __importDefault(require("../fs/findFilepath"));
19
+ const logging_1 = require("../console/logging");
20
+ const parse_1 = require("./parse");
21
+ const errors_2 = require("../console/errors");
22
+ const sendUpdates_1 = require("../api/sendUpdates");
23
+ function stageProject(settings, pkg) {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ if (!settings.dictionary) {
26
+ settings.dictionary = (0, findFilepath_1.default)([
27
+ './dictionary.js',
28
+ './src/dictionary.js',
29
+ './dictionary.json',
30
+ './src/dictionary.json',
31
+ './dictionary.ts',
32
+ './src/dictionary.ts',
33
+ ]);
34
+ }
35
+ // Separate defaultLocale from locales
36
+ settings.locales = settings.locales.filter((locale) => locale !== settings.defaultLocale);
37
+ // validate timeout
38
+ const timeout = parseInt(settings.timeout);
39
+ if (isNaN(timeout) || timeout < 0) {
40
+ (0, errors_1.logErrorAndExit)(`Invalid timeout: ${settings.timeout}. Must be a positive integer.`);
41
+ }
42
+ settings.timeout = timeout.toString();
43
+ // ---- CREATING UPDATES ---- //
44
+ const { updates, errors } = yield (0, parse_1.createUpdates)(settings, settings.dictionary, pkg);
45
+ if (errors.length > 0) {
46
+ if (settings.ignoreErrors) {
47
+ (0, logging_1.logWarning)(chalk_1.default.red(`CLI tool encountered errors while scanning for ${chalk_1.default.green('<T>')} tags. These components will not be translated.\n` +
48
+ errors
49
+ .map((error) => chalk_1.default.yellow('• Warning: ') + error + '\n')
50
+ .join('')));
51
+ }
52
+ else {
53
+ (0, logging_1.logError)(chalk_1.default.red(`CLI tool encountered errors while scanning for ${chalk_1.default.green('<T>')} tags. ${chalk_1.default.gray('To ignore these errors, re-run with --ignore-errors')}\n` +
54
+ errors
55
+ .map((error) => chalk_1.default.red('• Error: ') + error + '\n')
56
+ .join('')));
57
+ }
58
+ }
59
+ if (settings.dryRun) {
60
+ (0, logging_1.logSuccess)('Dry run: No translations were sent to General Translation.');
61
+ return null;
62
+ }
63
+ if (updates.length == 0) {
64
+ (0, logging_1.logError)(chalk_1.default.red(`No in-line content or dictionaries were found for ${chalk_1.default.green(pkg)}. Are you sure you're running this command in the right directory?`));
65
+ return null;
66
+ }
67
+ // Send updates to General Translation API
68
+ if (!settings.locales) {
69
+ (0, errors_1.logErrorAndExit)(errors_2.noLocalesError);
70
+ }
71
+ if (!settings.defaultLocale) {
72
+ (0, errors_1.logErrorAndExit)(errors_2.noDefaultLocaleError);
73
+ }
74
+ if (!settings.apiKey) {
75
+ (0, errors_1.logErrorAndExit)(errors_2.noApiKeyError);
76
+ }
77
+ if (!settings.projectId) {
78
+ (0, errors_1.logErrorAndExit)(errors_2.noProjectIdError);
79
+ }
80
+ const updateResponse = yield (0, sendUpdates_1.sendUpdates)(updates, Object.assign(Object.assign({}, settings), { timeout: settings.timeout, dataFormat: 'JSX' }), pkg);
81
+ const { versionId, locales } = updateResponse;
82
+ return { versionId, locales };
83
+ });
84
+ }
@@ -0,0 +1,2 @@
1
+ import { Options, Settings } from '../types';
2
+ export declare function translate(settings: Options & Settings, versionId: string): Promise<void>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.translate = translate;
13
+ const fetchTranslations_1 = require("../api/fetchTranslations");
14
+ const waitForUpdates_1 = require("../api/waitForUpdates");
15
+ const save_1 = require("../formats/gt/save");
16
+ function translate(settings, versionId) {
17
+ return __awaiter(this, void 0, void 0, function* () {
18
+ // timeout was validated earlier
19
+ const startTime = Date.now();
20
+ const timeout = parseInt(settings.timeout) * 1000;
21
+ const result = yield (0, waitForUpdates_1.waitForUpdates)(settings.apiKey, settings.baseUrl, versionId, startTime, timeout);
22
+ if (!result) {
23
+ process.exit(1);
24
+ }
25
+ const translations = yield (0, fetchTranslations_1.fetchTranslations)(settings.baseUrl, settings.apiKey, versionId);
26
+ // Save translations to local directory if files.gt.output is provided
27
+ if (settings.files && settings.files.placeholderPaths.gt) {
28
+ yield (0, save_1.saveTranslations)(translations, settings.files.placeholderPaths, 'JSX');
29
+ }
30
+ });
31
+ }
@@ -26,9 +26,8 @@ export type Options = {
26
26
  inline?: boolean;
27
27
  ignoreErrors: boolean;
28
28
  dryRun: boolean;
29
- wait: boolean;
30
29
  timeout: string;
31
- publish: boolean;
30
+ stageTranslations?: boolean;
32
31
  };
33
32
  export type WrapOptions = {
34
33
  src: string[];
@@ -92,8 +91,10 @@ export type Settings = {
92
91
  resolvedPaths: ResolvedFiles;
93
92
  placeholderPaths: ResolvedFiles;
94
93
  transformPaths: TransformFiles;
95
- };
96
- versionId?: string;
94
+ } | undefined;
95
+ stageTranslations: boolean;
96
+ _versionId?: string;
97
+ version?: string;
97
98
  description?: string;
98
99
  src?: string[];
99
100
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtx-cli",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "type": "commonjs",
5
5
  "main": "dist/index.js",
6
6
  "bin": "dist/main.js",