gtx-cli 2.1.5-alpha.5 → 2.1.5-alpha.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 (59) hide show
  1. package/dist/api/checkFileTranslations.d.ts +1 -4
  2. package/dist/api/checkFileTranslations.js +32 -35
  3. package/dist/api/downloadFileBatch.d.ts +1 -0
  4. package/dist/api/sendFiles.d.ts +2 -13
  5. package/dist/api/sendFiles.js +15 -8
  6. package/dist/cli/base.d.ts +5 -17
  7. package/dist/cli/base.js +57 -75
  8. package/dist/cli/commands/stage.d.ts +5 -0
  9. package/dist/cli/commands/stage.js +100 -0
  10. package/dist/cli/commands/translate.d.ts +6 -0
  11. package/dist/cli/commands/translate.js +58 -0
  12. package/dist/cli/flags.d.ts +3 -0
  13. package/dist/cli/flags.js +37 -0
  14. package/dist/cli/next.js +0 -1
  15. package/dist/cli/react.d.ts +2 -5
  16. package/dist/cli/react.js +13 -153
  17. package/dist/config/generateSettings.js +11 -6
  18. package/dist/console/index.d.ts +1 -0
  19. package/dist/console/index.js +1 -0
  20. package/dist/console/logging.d.ts +1 -0
  21. package/dist/console/logging.js +3 -0
  22. package/dist/formats/files/fileMapping.d.ts +2 -1
  23. package/dist/formats/files/fileMapping.js +6 -0
  24. package/dist/formats/files/translate.d.ts +4 -13
  25. package/dist/formats/files/translate.js +30 -142
  26. package/dist/formats/files/upload.js +1 -75
  27. package/dist/formats/gt/save.d.ts +1 -2
  28. package/dist/formats/gt/save.js +2 -5
  29. package/dist/fs/config/setupConfig.d.ts +1 -0
  30. package/dist/fs/config/setupConfig.js +1 -0
  31. package/dist/fs/config/updateConfig.d.ts +1 -2
  32. package/dist/fs/config/updateConfig.js +1 -1
  33. package/dist/fs/config/updateVersions.d.ts +10 -0
  34. package/dist/fs/config/updateVersions.js +30 -0
  35. package/dist/fs/copyFile.d.ts +1 -2
  36. package/dist/translation/parse.d.ts +2 -2
  37. package/dist/translation/parse.js +4 -6
  38. package/dist/translation/stage.d.ts +2 -5
  39. package/dist/translation/stage.js +13 -55
  40. package/dist/types/files.d.ts +1 -0
  41. package/dist/types/files.js +1 -0
  42. package/dist/types/index.d.ts +27 -3
  43. package/dist/utils/flattenJsonFiles.d.ts +2 -2
  44. package/dist/utils/hash.d.ts +6 -0
  45. package/dist/utils/hash.js +11 -0
  46. package/dist/utils/localizeStaticImports.d.ts +2 -2
  47. package/dist/utils/localizeStaticUrls.d.ts +2 -2
  48. package/dist/utils/localizeStaticUrls.js +22 -16
  49. package/package.json +2 -2
  50. package/dist/api/downloadFile.d.ts +0 -9
  51. package/dist/api/downloadFile.js +0 -74
  52. package/dist/api/fetchTranslations.d.ts +0 -7
  53. package/dist/api/fetchTranslations.js +0 -18
  54. package/dist/api/sendUpdates.d.ts +0 -19
  55. package/dist/api/sendUpdates.js +0 -48
  56. package/dist/api/waitForUpdates.d.ts +0 -9
  57. package/dist/api/waitForUpdates.js +0 -89
  58. package/dist/translation/translate.d.ts +0 -2
  59. package/dist/translation/translate.js +0 -18
@@ -20,7 +20,4 @@ export declare function checkFileTranslations(data: {
20
20
  versionId: string;
21
21
  fileName: string;
22
22
  };
23
- }, locales: string[], timeoutDuration: number, resolveOutputPath: (sourcePath: string, locale: string) => string, downloadStatus: {
24
- downloaded: Set<string>;
25
- failed: Set<string>;
26
- }, options: Settings): Promise<boolean>;
23
+ }, locales: string[], timeoutDuration: number, resolveOutputPath: (sourcePath: string, locale: string) => string | null, options: Settings): Promise<boolean>;
@@ -1,9 +1,9 @@
1
1
  import chalk from 'chalk';
2
2
  import { createOraSpinner, logError } from '../console/logging.js';
3
3
  import { getLocaleProperties } from 'generaltranslation';
4
- import { downloadFile } from './downloadFile.js';
5
4
  import { downloadFileBatch } from './downloadFileBatch.js';
6
5
  import { gt } from '../utils/gt.js';
6
+ import { TEMPLATE_FILE_NAME } from '../cli/commands/stage.js';
7
7
  /**
8
8
  * Checks the status of translations for a given version ID
9
9
  * @param apiKey - The API key for the General Translation API
@@ -14,13 +14,18 @@ import { gt } from '../utils/gt.js';
14
14
  * @param timeoutDuration - The timeout duration for the wait in seconds
15
15
  * @returns True if all translations are deployed, false otherwise
16
16
  */
17
- export async function checkFileTranslations(data, locales, timeoutDuration, resolveOutputPath, downloadStatus, options) {
17
+ export async function checkFileTranslations(data, locales, timeoutDuration, resolveOutputPath, options) {
18
18
  const startTime = Date.now();
19
19
  console.log();
20
20
  const spinner = await createOraSpinner();
21
21
  spinner.start('Waiting for translation...');
22
22
  // Initialize the query data
23
23
  const fileQueryData = prepareFileQueryData(data, locales);
24
+ const downloadStatus = {
25
+ downloaded: new Set(),
26
+ failed: new Set(),
27
+ skipped: new Set(),
28
+ };
24
29
  // Do first check immediately
25
30
  const initialCheck = await checkTranslationDeployment(fileQueryData, downloadStatus, spinner, resolveOutputPath, options);
26
31
  if (initialCheck) {
@@ -146,7 +151,8 @@ function generateStatusSuffixText(downloadStatus, fileQueryData) {
146
151
  localeStatuses.push(chalk.yellow(`${pendingCodes}`));
147
152
  }
148
153
  // Format the line
149
- newSuffixText.push(`${chalk.bold(fileName)} [${localeStatuses.join(', ')}]`);
154
+ const prettyFileName = fileName === TEMPLATE_FILE_NAME ? '<React Elements>' : fileName;
155
+ newSuffixText.push(`${chalk.bold(prettyFileName)} [${localeStatuses.join(', ')}]`);
150
156
  }
151
157
  // If we couldn't show all files, add an indicator
152
158
  if (filesArray.length > maxFilesToShow) {
@@ -161,7 +167,8 @@ async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner
161
167
  try {
162
168
  // Only query for files that haven't been downloaded yet
163
169
  const currentQueryData = fileQueryData.filter((item) => !downloadStatus.downloaded.has(`${item.fileName}:${item.locale}`) &&
164
- !downloadStatus.failed.has(`${item.fileName}:${item.locale}`));
170
+ !downloadStatus.failed.has(`${item.fileName}:${item.locale}`) &&
171
+ !downloadStatus.skipped.has(`${item.fileName}:${item.locale}`));
165
172
  // If all files have been downloaded, we're done
166
173
  if (currentQueryData.length === 0) {
167
174
  return true;
@@ -173,11 +180,17 @@ async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner
173
180
  const readyTranslations = translations.filter((translation) => translation.isReady && translation.fileName);
174
181
  if (readyTranslations.length > 0) {
175
182
  // Prepare batch download data
176
- const batchFiles = readyTranslations.map((translation) => {
183
+ const batchFiles = readyTranslations
184
+ .map((translation) => {
177
185
  const locale = translation.locale;
178
186
  const fileName = translation.fileName;
179
187
  const translationId = translation.id;
180
188
  const outputPath = resolveOutputPath(fileName, locale);
189
+ // Skip downloading GTJSON files that are not in the files configuration
190
+ if (outputPath === null) {
191
+ downloadStatus.skipped.add(`${fileName}:${locale}`);
192
+ return null;
193
+ }
181
194
  return {
182
195
  translationId,
183
196
  inputPath: fileName,
@@ -185,44 +198,28 @@ async function checkTranslationDeployment(fileQueryData, downloadStatus, spinner
185
198
  locale,
186
199
  fileLocale: `${fileName}:${locale}`,
187
200
  };
188
- });
189
- // Use batch download if there are multiple files
190
- if (batchFiles.length > 1) {
191
- const batchResult = await downloadFileBatch(batchFiles.map(({ translationId, outputPath, inputPath, locale }) => ({
192
- translationId,
193
- outputPath,
194
- inputPath,
195
- locale,
196
- })), options);
197
- // Process results
198
- batchFiles.forEach((file) => {
199
- const { translationId, fileLocale } = file;
200
- if (batchResult.successful.includes(translationId)) {
201
- downloadStatus.downloaded.add(fileLocale);
202
- }
203
- else if (batchResult.failed.includes(translationId)) {
204
- downloadStatus.failed.add(fileLocale);
205
- }
206
- });
207
- }
208
- else if (batchFiles.length === 1) {
209
- // For a single file, use the original downloadFile method
210
- const file = batchFiles[0];
211
- const result = await downloadFile(file.translationId, file.outputPath, file.inputPath, file.locale, options);
212
- if (result) {
213
- downloadStatus.downloaded.add(file.fileLocale);
201
+ })
202
+ .filter((file) => file !== null);
203
+ const batchResult = await downloadFileBatch(batchFiles, options);
204
+ // Process results
205
+ batchFiles.forEach((file) => {
206
+ const { translationId, fileLocale } = file;
207
+ if (batchResult.successful.includes(translationId)) {
208
+ downloadStatus.downloaded.add(fileLocale);
214
209
  }
215
- else {
216
- downloadStatus.failed.add(file.fileLocale);
210
+ else if (batchResult.failed.includes(translationId)) {
211
+ downloadStatus.failed.add(fileLocale);
217
212
  }
218
- }
213
+ });
219
214
  }
220
215
  // Force a refresh of the spinner display
221
216
  const statusText = generateStatusSuffixText(downloadStatus, fileQueryData);
222
217
  // Clear and reapply the suffix to force a refresh
223
218
  spinner.text = statusText;
224
219
  // If all files have been downloaded, we're done
225
- if (downloadStatus.downloaded.size + downloadStatus.failed.size ===
220
+ if (downloadStatus.downloaded.size +
221
+ downloadStatus.failed.size +
222
+ downloadStatus.skipped.size ===
226
223
  fileQueryData.length) {
227
224
  return true;
228
225
  }
@@ -4,6 +4,7 @@ export type BatchedFiles = Array<{
4
4
  outputPath: string;
5
5
  inputPath: string;
6
6
  locale: string;
7
+ fileLocale: string;
7
8
  }>;
8
9
  export type DownloadFileBatchResult = {
9
10
  successful: string[];
@@ -1,9 +1,5 @@
1
- import { Settings } from '../types/index.js';
1
+ import { Settings, TranslateFlags } from '../types/index.js';
2
2
  import { CompletedFileTranslationData, FileToTranslate } from 'generaltranslation/types';
3
- export type ApiOptions = Settings & {
4
- publish: boolean;
5
- wait: boolean;
6
- };
7
3
  export type SendFilesResult = {
8
4
  data: Record<string, {
9
5
  fileName: string;
@@ -18,11 +14,4 @@ export type SendFilesResult = {
18
14
  * @param options - The options for the API call
19
15
  * @returns The translated content or version ID
20
16
  */
21
- export declare function sendFiles(files: FileToTranslate[], options: ApiOptions): Promise<{
22
- data: Record<string, {
23
- fileName: string;
24
- versionId: string;
25
- }>;
26
- locales: string[];
27
- translations: CompletedFileTranslationData[];
28
- }>;
17
+ export declare function sendFiles(files: FileToTranslate[], options: TranslateFlags, settings: Settings): Promise<SendFilesResult>;
@@ -1,27 +1,34 @@
1
1
  import chalk from 'chalk';
2
2
  import { createSpinner, logMessage, logSuccess } from '../console/logging.js';
3
3
  import { gt } from '../utils/gt.js';
4
+ import { TEMPLATE_FILE_NAME } from '../cli/commands/stage.js';
4
5
  /**
5
6
  * Sends multiple files for translation to the API
6
7
  * @param files - Array of file objects to translate
7
8
  * @param options - The options for the API call
8
9
  * @returns The translated content or version ID
9
10
  */
10
- export async function sendFiles(files, options) {
11
+ export async function sendFiles(files, options, settings) {
11
12
  logMessage(chalk.cyan('Files to translate:') +
12
13
  '\n' +
13
- files.map((file) => ` - ${chalk.bold(file.fileName)}`).join('\n'));
14
+ files
15
+ .map((file) => {
16
+ if (file.fileName === TEMPLATE_FILE_NAME) {
17
+ return `- <React Elements>`;
18
+ }
19
+ return `- ${file.fileName}`;
20
+ })
21
+ .join('\n'));
14
22
  const spinner = createSpinner('dots');
15
23
  spinner.start(`Sending ${files.length} file${files.length !== 1 ? 's' : ''} to General Translation API...`);
16
24
  try {
17
25
  // Send the files to the API
18
26
  const responseData = await gt.enqueueFiles(files, {
19
- publish: options.publish,
20
- description: options.description,
21
- sourceLocale: options.defaultLocale,
22
- targetLocales: options.locales,
23
- _versionId: options._versionId,
24
- modelProvider: options.modelProvider,
27
+ publish: settings.publish,
28
+ sourceLocale: settings.defaultLocale,
29
+ targetLocales: settings.locales,
30
+ version: settings.version, // not set ATM
31
+ modelProvider: settings.modelProvider,
25
32
  });
26
33
  // Handle version ID response (for async processing)
27
34
  const { data, message, locales, translations } = responseData;
@@ -1,25 +1,11 @@
1
1
  import { Command } from 'commander';
2
- import { Settings, SupportedLibraries, SetupOptions } from '../types/index.js';
2
+ import { Settings, SupportedLibraries, SetupOptions, TranslateFlags } from '../types/index.js';
3
3
  export type UploadOptions = {
4
4
  config?: string;
5
5
  apiKey?: string;
6
6
  projectId?: string;
7
7
  defaultLocale?: string;
8
8
  };
9
- export type TranslateOptions = {
10
- config?: string;
11
- defaultLocale?: string;
12
- locales?: string[];
13
- apiKey?: string;
14
- projectId?: string;
15
- dryRun: boolean;
16
- experimentalLocalizeStaticUrls?: boolean;
17
- experimentalHideDefaultLocale?: boolean;
18
- experimentalFlattenJsonFiles?: boolean;
19
- experimentalLocalizeStaticImports?: boolean;
20
- excludeStaticUrls?: string[];
21
- excludeStaticImports?: string[];
22
- };
23
9
  export type LoginOptions = {
24
10
  keyType?: 'development' | 'production';
25
11
  };
@@ -30,14 +16,16 @@ export declare class BaseCLI {
30
16
  constructor(program: Command, library: SupportedLibraries, additionalModules?: SupportedLibraries[]);
31
17
  init(): void;
32
18
  execute(): void;
33
- protected setupGTCommand(): void;
19
+ protected setupStageCommand(): void;
20
+ protected setupTranslateCommand(): void;
21
+ protected handleStage(initOptions: TranslateFlags): Promise<void>;
22
+ protected handleTranslate(initOptions: TranslateFlags): Promise<void>;
34
23
  protected setupUploadCommand(): void;
35
24
  protected setupLoginCommand(): void;
36
25
  protected setupInitCommand(): void;
37
26
  protected setupConfigureCommand(): void;
38
27
  protected setupSetupCommand(): void;
39
28
  protected handleUploadCommand(settings: Settings & UploadOptions): Promise<void>;
40
- protected handleGenericTranslate(settings: Settings & TranslateOptions): Promise<void>;
41
29
  protected handleSetupReactCommand(options: SetupOptions): Promise<void>;
42
30
  protected handleInitCommand(ranReactSetup: boolean): Promise<void>;
43
31
  protected handleLoginCommand(options: LoginOptions): Promise<void>;
package/dist/cli/base.js CHANGED
@@ -5,7 +5,6 @@ import path from 'node:path';
5
5
  import fs from 'node:fs';
6
6
  import { generateSettings } from '../config/generateSettings.js';
7
7
  import chalk from 'chalk';
8
- import { translateFiles } from '../formats/files/translate.js';
9
8
  import { FILE_EXT_TO_EXT_LABEL } from '../formats/files/supportedFiles.js';
10
9
  import { handleSetupReactCommand } from '../setup/wizard.js';
11
10
  import { isPackageInstalled, searchForPackageJson, } from '../utils/packageJson.js';
@@ -14,11 +13,12 @@ import { installPackage } from '../utils/installPackage.js';
14
13
  import { getPackageManager } from '../utils/packageManager.js';
15
14
  import { retrieveCredentials, setCredentials } from '../utils/credentials.js';
16
15
  import { areCredentialsSet } from '../utils/credentials.js';
17
- import localizeStaticUrls from '../utils/localizeStaticUrls.js';
18
- import flattenJsonFiles from '../utils/flattenJsonFiles.js';
19
- import localizeStaticImports from '../utils/localizeStaticImports.js';
20
- import copyFile from '../fs/copyFile.js';
21
16
  import { upload } from '../formats/files/upload.js';
17
+ import { attachTranslateFlags } from './flags.js';
18
+ import { handleStage } from './commands/stage.js';
19
+ import { handleDownload, handleTranslate, postProcessTranslations, } from './commands/translate.js';
20
+ import localizeStaticUrls from '../utils/localizeStaticUrls.js';
21
+ import updateConfig from '../fs/config/updateConfig.js';
22
22
  export class BaseCLI {
23
23
  library;
24
24
  additionalModules;
@@ -36,7 +36,7 @@ export class BaseCLI {
36
36
  }
37
37
  // Init is never called in a child class
38
38
  init() {
39
- this.setupGTCommand();
39
+ this.setupTranslateCommand();
40
40
  }
41
41
  // Execute is called by the main program
42
42
  execute() {
@@ -45,28 +45,57 @@ export class BaseCLI {
45
45
  process.argv.push('init');
46
46
  }
47
47
  }
48
- setupGTCommand() {
49
- this.program
48
+ setupStageCommand() {
49
+ attachTranslateFlags(this.program
50
+ .command('stage')
51
+ .description('Submits the project to the General Translation API for translation. Translations created using this command will require human approval.')).action(async (initOptions) => {
52
+ displayHeader('Staging project for translation with approval required...');
53
+ await this.handleStage(initOptions);
54
+ endCommand('Done!');
55
+ });
56
+ }
57
+ setupTranslateCommand() {
58
+ attachTranslateFlags(this.program
50
59
  .command('translate')
51
- .description('Translate your project using General Translation')
52
- .option('-c, --config <path>', 'Filepath to config file, by default gt.config.json', findFilepath(['gt.config.json']))
53
- .option('--api-key <key>', 'API key for General Translation cloud service')
54
- .option('--project-id <id>', 'Project ID for the translation service')
55
- .option('--default-language, --default-locale <locale>', 'Default locale (e.g., en)')
56
- .option('--new, --locales <locales...>', 'Space-separated list of locales (e.g., en fr es)')
57
- .option('--dry-run', 'Dry run, does not send updates to General Translation API', false)
58
- .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)
59
- .option('--experimental-hide-default-locale', 'When localizing static locales, hide the default locale from the path', false)
60
- .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)
61
- .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)
62
- .action(async (initOptions) => {
60
+ .description('Translate your project using General Translation')).action(async (initOptions) => {
63
61
  displayHeader('Starting translation...');
64
- const settings = await generateSettings(initOptions);
65
- const options = { ...initOptions, ...settings };
66
- await this.handleGenericTranslate(options);
62
+ await this.handleTranslate(initOptions);
67
63
  endCommand('Done!');
68
64
  });
69
65
  }
66
+ async handleStage(initOptions) {
67
+ const settings = await generateSettings(initOptions);
68
+ if (!settings.stageTranslations) {
69
+ // Update settings.stageTranslations to true
70
+ settings.stageTranslations = true;
71
+ await updateConfig({
72
+ configFilepath: settings.config,
73
+ stageTranslations: true,
74
+ });
75
+ }
76
+ await handleStage(initOptions, settings, this.library, true);
77
+ }
78
+ async handleTranslate(initOptions) {
79
+ const settings = await generateSettings(initOptions);
80
+ if (!settings.stageTranslations) {
81
+ const results = await handleStage(initOptions, settings, this.library, false);
82
+ // Process default locale static URLs first, before translations
83
+ if (settings.options?.experimentalLocalizeStaticUrls) {
84
+ await localizeStaticUrls(settings, [settings.defaultLocale]);
85
+ }
86
+ if (results) {
87
+ await handleTranslate(initOptions, settings, results);
88
+ }
89
+ }
90
+ else {
91
+ // Process default locale static URLs first, before downloading translations
92
+ if (settings.options?.experimentalLocalizeStaticUrls) {
93
+ await localizeStaticUrls(settings, [settings.defaultLocale]);
94
+ }
95
+ await handleDownload(initOptions, settings);
96
+ }
97
+ await postProcessTranslations(settings);
98
+ }
70
99
  setupUploadCommand() {
71
100
  this.program
72
101
  .command('upload')
@@ -197,55 +226,6 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
197
226
  // Process all file types at once with a single call
198
227
  await upload(sourceFiles, placeholderPaths, transformPaths, dataFormat, settings);
199
228
  }
200
- async handleGenericTranslate(settings) {
201
- // dataFormat for JSONs
202
- let dataFormat;
203
- if (this.library === 'next-intl') {
204
- dataFormat = 'ICU';
205
- }
206
- else if (this.library === 'i18next') {
207
- if (this.additionalModules.includes('i18next-icu')) {
208
- dataFormat = 'ICU';
209
- }
210
- else {
211
- dataFormat = 'I18NEXT';
212
- }
213
- }
214
- else {
215
- dataFormat = 'JSX';
216
- }
217
- if (!settings.files ||
218
- (Object.keys(settings.files.placeholderPaths).length === 1 &&
219
- settings.files.placeholderPaths.gt)) {
220
- return;
221
- }
222
- const { resolvedPaths: sourceFiles, placeholderPaths, transformPaths, } = settings.files;
223
- // Localize static urls in default locale BEFORE translation
224
- if (settings.experimentalLocalizeStaticUrls) {
225
- await localizeStaticUrls(settings, [settings.defaultLocale]);
226
- }
227
- // Process all file types at once with a single call
228
- await translateFiles(sourceFiles, placeholderPaths, transformPaths, dataFormat, settings);
229
- // Localize static urls in other locales AFTER translation
230
- if (settings.experimentalLocalizeStaticUrls) {
231
- const otherLocales = settings.locales.filter((locale) => locale !== settings.defaultLocale);
232
- if (otherLocales.length > 0) {
233
- await localizeStaticUrls(settings, otherLocales);
234
- }
235
- }
236
- // Localize static imports (/docs -> /[locale]/docs)
237
- if (settings.experimentalLocalizeStaticImports) {
238
- await localizeStaticImports(settings);
239
- }
240
- // Flatten json files into a single file
241
- if (settings.experimentalFlattenJsonFiles) {
242
- await flattenJsonFiles(settings);
243
- }
244
- // Copy files to the target locale
245
- if (settings.options?.copyFiles) {
246
- await copyFile(settings);
247
- }
248
- }
249
229
  async handleSetupReactCommand(options) {
250
230
  await handleSetupReactCommand(options);
251
231
  }
@@ -265,13 +245,14 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
265
245
  const usingCDN = isUsingGT
266
246
  ? await promptConfirm({
267
247
  message: `Auto-detected that you're using gt-next or gt-react. Would you like to use the General Translation CDN to store your translations?\nSee ${isUsingGTNext
268
- ? 'https://generaltranslation.com/docs/next/reference/local-tx'
269
- : 'https://generaltranslation.com/docs/react/reference/local-tx'} for more information.\nIf you answer no, we'll configure the CLI tool to download completed translations.`,
248
+ ? 'https://generaltranslation.com/en/docs/next/guides/local-tx'
249
+ : 'https://generaltranslation.com/en/docs/react/guides/local-tx'} for more information.\nIf you answer no, we'll configure the CLI tool to download completed translations.`,
270
250
  defaultValue: true,
271
251
  })
272
252
  : false;
273
253
  if (isUsingGT && !usingCDN) {
274
- logMessage(`To prevent translations from being published, please disable the project setting on the dashboard: ${chalk.cyan('https://dash.generaltranslation.com/settings/project')}`);
254
+ logMessage(`Make sure to add a loadTranslations function to your app configuration to correctly use local translations.
255
+ See https://generaltranslation.com/en/docs/next/guides/local-tx`);
275
256
  }
276
257
  // Ask where the translations are stored
277
258
  const translationsDir = isUsingGT && !usingCDN
@@ -320,6 +301,7 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
320
301
  defaultLocale,
321
302
  locales,
322
303
  files: Object.keys(files).length > 0 ? files : undefined,
304
+ publish: isUsingGT && usingCDN,
323
305
  });
324
306
  logSuccess(`Feel free to edit ${chalk.cyan(configFilepath)} to customize your translation setup. Docs: https://generaltranslation.com/docs/cli/reference/config`);
325
307
  // Install gtx-cli if not installed
@@ -0,0 +1,5 @@
1
+ import { Settings, SupportedLibraries, TranslateFlags } from '../../types/index.js';
2
+ import { SendFilesResult } from '../../api/sendFiles.js';
3
+ export declare const TEMPLATE_FILE_NAME = "__INTERNAL_GT_TEMPLATE_NAME__";
4
+ export declare const TEMPLATE_FILE_ID: string;
5
+ export declare function handleStage(options: TranslateFlags, settings: Settings, library: SupportedLibraries, stage: boolean): Promise<SendFilesResult | undefined>;
@@ -0,0 +1,100 @@
1
+ import { logErrorAndExit, logSuccess } from '../../console/logging.js';
2
+ import { noLocalesError, noDefaultLocaleError, noApiKeyError, noProjectIdError, devApiKeyError, invalidConfigurationError, } from '../../console/index.js';
3
+ import { aggregateFiles } from '../../formats/files/translate.js';
4
+ import { aggregateReactTranslations } from '../../translation/stage.js';
5
+ import { sendFiles } from '../../api/sendFiles.js';
6
+ import { updateVersions } from '../../fs/config/updateVersions.js';
7
+ import updateConfig from '../../fs/config/updateConfig.js';
8
+ import { hashStringSync } from '../../utils/hash.js';
9
+ export const TEMPLATE_FILE_NAME = '__INTERNAL_GT_TEMPLATE_NAME__';
10
+ export const TEMPLATE_FILE_ID = hashStringSync(TEMPLATE_FILE_NAME);
11
+ export async function handleStage(options, settings, library, stage) {
12
+ // Validate required settings are present if not in dry run
13
+ if (!options.dryRun) {
14
+ if (!settings.locales) {
15
+ logErrorAndExit(noLocalesError);
16
+ }
17
+ if (!settings.defaultLocale) {
18
+ logErrorAndExit(noDefaultLocaleError);
19
+ }
20
+ if (!settings.apiKey) {
21
+ logErrorAndExit(noApiKeyError);
22
+ }
23
+ if (settings.apiKey.startsWith('gtx-dev-')) {
24
+ logErrorAndExit(devApiKeyError);
25
+ }
26
+ if (!settings.projectId) {
27
+ logErrorAndExit(noProjectIdError);
28
+ }
29
+ }
30
+ // Aggregate files
31
+ const allFiles = await aggregateFiles(settings);
32
+ // Parse for React components
33
+ let reactComponents = 0;
34
+ if (library === 'gt-react' || library === 'gt-next') {
35
+ const updates = await aggregateReactTranslations(options, settings, library);
36
+ if (updates.length > 0) {
37
+ if (!options.dryRun &&
38
+ !settings.publish &&
39
+ !settings.files?.placeholderPaths.gt) {
40
+ logErrorAndExit(invalidConfigurationError);
41
+ }
42
+ reactComponents = updates.length;
43
+ // Convert updates to a file object
44
+ const fileData = {};
45
+ const fileMetadata = {};
46
+ // Convert updates to the proper data format
47
+ for (const update of updates) {
48
+ const { source, metadata, dataFormat } = update;
49
+ metadata.dataFormat = dataFormat; // add the data format to the metadata
50
+ const { hash, id } = metadata;
51
+ if (id) {
52
+ fileData[id] = source;
53
+ fileMetadata[id] = metadata;
54
+ }
55
+ else {
56
+ fileData[hash] = source;
57
+ fileMetadata[hash] = metadata;
58
+ }
59
+ }
60
+ allFiles.push({
61
+ fileName: TEMPLATE_FILE_NAME,
62
+ content: JSON.stringify(fileData),
63
+ fileFormat: 'GTJSON',
64
+ formatMetadata: fileMetadata,
65
+ });
66
+ }
67
+ }
68
+ // Dry run
69
+ if (options.dryRun) {
70
+ const fileNames = allFiles
71
+ .map((file) => {
72
+ if (file.fileName === TEMPLATE_FILE_NAME) {
73
+ return `- <React Elements> (${reactComponents})`;
74
+ }
75
+ return `- ${file.fileName}`;
76
+ })
77
+ .join('\n');
78
+ logSuccess(`Dry run: No files were sent to General Translation. Found files:\n${fileNames}`);
79
+ return undefined;
80
+ }
81
+ // Send translations to General Translation
82
+ let filesTranslationResponse;
83
+ if (allFiles.length > 0) {
84
+ filesTranslationResponse = await sendFiles(allFiles, options, settings);
85
+ if (stage) {
86
+ await updateVersions({
87
+ configDirectory: settings.configDirectory,
88
+ versionData: filesTranslationResponse.data,
89
+ });
90
+ }
91
+ const templateData = filesTranslationResponse.data[TEMPLATE_FILE_ID];
92
+ if (templateData?.versionId) {
93
+ await updateConfig({
94
+ configFilepath: settings.config,
95
+ _versionId: templateData.versionId,
96
+ });
97
+ }
98
+ }
99
+ return filesTranslationResponse;
100
+ }
@@ -0,0 +1,6 @@
1
+ import { SendFilesResult } from '../../api/sendFiles.js';
2
+ import { TranslateFlags } from '../../types/index.js';
3
+ import { Settings } from '../../types/index.js';
4
+ export declare function handleTranslate(options: TranslateFlags, settings: Settings, filesTranslationResponse: SendFilesResult | undefined): Promise<void>;
5
+ export declare function handleDownload(options: TranslateFlags, settings: Settings): Promise<void>;
6
+ export declare function postProcessTranslations(settings: Settings): Promise<void>;
@@ -0,0 +1,58 @@
1
+ import { checkFileTranslations } from '../../api/checkFileTranslations.js';
2
+ import { createFileMapping } from '../../formats/files/fileMapping.js';
3
+ import { logError } from '../../console/logging.js';
4
+ import { getStagedVersions } from '../../fs/config/updateVersions.js';
5
+ import copyFile from '../../fs/copyFile.js';
6
+ import localizeStaticImports from '../../utils/localizeStaticImports.js';
7
+ import flattenJsonFiles from '../../utils/flattenJsonFiles.js';
8
+ import localizeStaticUrls from '../../utils/localizeStaticUrls.js';
9
+ import { noFilesError, noVersionIdError } from '../../console/index.js';
10
+ // Downloads translations that were completed
11
+ export async function handleTranslate(options, settings, filesTranslationResponse) {
12
+ if (filesTranslationResponse && settings.files) {
13
+ const { resolvedPaths, placeholderPaths, transformPaths } = settings.files;
14
+ const fileMapping = createFileMapping(resolvedPaths, placeholderPaths, transformPaths, settings.locales, settings.defaultLocale);
15
+ const { data } = filesTranslationResponse;
16
+ // Check for remaining translations
17
+ await checkFileTranslations(data, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings);
18
+ }
19
+ }
20
+ // Downloads translations that were originally staged
21
+ export async function handleDownload(options, settings) {
22
+ if (!settings._versionId) {
23
+ logError(noVersionIdError);
24
+ process.exit(1);
25
+ }
26
+ if (!settings.files) {
27
+ logError(noFilesError);
28
+ process.exit(1);
29
+ }
30
+ // Files
31
+ const { resolvedPaths, placeholderPaths, transformPaths } = settings.files;
32
+ const fileMapping = createFileMapping(resolvedPaths, placeholderPaths, transformPaths, settings.locales, settings.defaultLocale);
33
+ const stagedVersionData = await getStagedVersions(settings.configDirectory);
34
+ // Check for remaining translations
35
+ await checkFileTranslations(stagedVersionData, settings.locales, options.timeout, (sourcePath, locale) => fileMapping[locale][sourcePath] ?? null, settings);
36
+ }
37
+ export async function postProcessTranslations(settings) {
38
+ // Localize static urls (/docs -> /[locale]/docs) for non-default locales only
39
+ // Default locale is processed earlier in the flow in base.ts
40
+ if (settings.options?.experimentalLocalizeStaticUrls) {
41
+ const nonDefaultLocales = settings.locales.filter(locale => locale !== settings.defaultLocale);
42
+ if (nonDefaultLocales.length > 0) {
43
+ await localizeStaticUrls(settings, nonDefaultLocales);
44
+ }
45
+ }
46
+ // Localize static imports (/docs -> /[locale]/docs)
47
+ if (settings.options?.experimentalLocalizeStaticImports) {
48
+ await localizeStaticImports(settings);
49
+ }
50
+ // Flatten json files into a single file
51
+ if (settings.options?.experimentalFlattenJsonFiles) {
52
+ await flattenJsonFiles(settings);
53
+ }
54
+ // Copy files to the target locale
55
+ if (settings.options?.copyFiles) {
56
+ await copyFile(settings);
57
+ }
58
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function attachTranslateFlags(command: Command): Command;
3
+ export declare function attachAdditionalReactTranslateFlags(command: Command): Command;