gtx-cli 2.1.4 → 2.1.5-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/api/checkFileTranslations.d.ts +1 -4
  3. package/dist/api/checkFileTranslations.js +32 -35
  4. package/dist/api/downloadFileBatch.d.ts +1 -0
  5. package/dist/api/sendFiles.d.ts +2 -13
  6. package/dist/api/sendFiles.js +15 -8
  7. package/dist/cli/base.d.ts +5 -17
  8. package/dist/cli/base.js +48 -68
  9. package/dist/cli/commands/stage.d.ts +5 -0
  10. package/dist/cli/commands/stage.js +100 -0
  11. package/dist/cli/commands/translate.d.ts +6 -0
  12. package/dist/cli/commands/translate.js +54 -0
  13. package/dist/cli/flags.d.ts +3 -0
  14. package/dist/cli/flags.js +37 -0
  15. package/dist/cli/next.js +0 -1
  16. package/dist/cli/react.d.ts +2 -5
  17. package/dist/cli/react.js +13 -153
  18. package/dist/config/generateSettings.js +11 -6
  19. package/dist/console/index.d.ts +1 -0
  20. package/dist/console/index.js +1 -0
  21. package/dist/console/logging.d.ts +1 -0
  22. package/dist/console/logging.js +3 -0
  23. package/dist/formats/files/fileMapping.d.ts +2 -1
  24. package/dist/formats/files/fileMapping.js +6 -0
  25. package/dist/formats/files/translate.d.ts +4 -13
  26. package/dist/formats/files/translate.js +30 -142
  27. package/dist/formats/files/upload.js +1 -75
  28. package/dist/formats/gt/save.d.ts +1 -2
  29. package/dist/formats/gt/save.js +2 -5
  30. package/dist/fs/config/setupConfig.d.ts +1 -0
  31. package/dist/fs/config/setupConfig.js +1 -0
  32. package/dist/fs/config/updateConfig.d.ts +1 -2
  33. package/dist/fs/config/updateConfig.js +1 -1
  34. package/dist/fs/config/updateVersions.d.ts +10 -0
  35. package/dist/fs/config/updateVersions.js +30 -0
  36. package/dist/fs/copyFile.d.ts +1 -2
  37. package/dist/translation/parse.d.ts +2 -2
  38. package/dist/translation/parse.js +4 -6
  39. package/dist/translation/stage.d.ts +2 -5
  40. package/dist/translation/stage.js +13 -55
  41. package/dist/types/files.d.ts +1 -0
  42. package/dist/types/files.js +1 -0
  43. package/dist/types/index.d.ts +27 -3
  44. package/dist/utils/flattenJsonFiles.d.ts +2 -2
  45. package/dist/utils/hash.d.ts +6 -0
  46. package/dist/utils/hash.js +11 -0
  47. package/dist/utils/localizeStaticImports.d.ts +2 -2
  48. package/dist/utils/localizeStaticUrls.d.ts +2 -2
  49. package/dist/utils/localizeStaticUrls.js +7 -3
  50. package/package.json +1 -1
  51. package/dist/api/downloadFile.d.ts +0 -9
  52. package/dist/api/downloadFile.js +0 -74
  53. package/dist/api/fetchTranslations.d.ts +0 -7
  54. package/dist/api/fetchTranslations.js +0 -18
  55. package/dist/api/sendUpdates.d.ts +0 -19
  56. package/dist/api/sendUpdates.js +0 -48
  57. package/dist/api/waitForUpdates.d.ts +0 -9
  58. package/dist/api/waitForUpdates.js +0 -89
  59. package/dist/translation/translate.d.ts +0 -2
  60. package/dist/translation/translate.js +0 -18
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.1.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#576](https://github.com/generaltranslation/gt/pull/576) [`be9c1ff`](https://github.com/generaltranslation/gt/commit/be9c1ff24c9a15a35e3f0da26e9ec941e5b41eea) Thanks [@SamEggert](https://github.com/SamEggert)! - Fix url localization
8
+
3
9
  ## 2.1.4
4
10
 
5
11
  ### Patch Changes
@@ -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,11 @@ 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 updateConfig from '../fs/config/updateConfig.js';
22
21
  export class BaseCLI {
23
22
  library;
24
23
  additionalModules;
@@ -36,7 +35,7 @@ export class BaseCLI {
36
35
  }
37
36
  // Init is never called in a child class
38
37
  init() {
39
- this.setupGTCommand();
38
+ this.setupTranslateCommand();
40
39
  }
41
40
  // Execute is called by the main program
42
41
  execute() {
@@ -45,28 +44,49 @@ export class BaseCLI {
45
44
  process.argv.push('init');
46
45
  }
47
46
  }
48
- setupGTCommand() {
49
- this.program
47
+ setupStageCommand() {
48
+ attachTranslateFlags(this.program
49
+ .command('stage')
50
+ .description('Submits the project to the General Translation API for translation. Translations created using this command will require human approval.')).action(async (initOptions) => {
51
+ displayHeader('Staging project for translation with approval required...');
52
+ await this.handleStage(initOptions);
53
+ endCommand('Done!');
54
+ });
55
+ }
56
+ setupTranslateCommand() {
57
+ attachTranslateFlags(this.program
50
58
  .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) => {
59
+ .description('Translate your project using General Translation')).action(async (initOptions) => {
63
60
  displayHeader('Starting translation...');
64
- const settings = await generateSettings(initOptions);
65
- const options = { ...initOptions, ...settings };
66
- await this.handleGenericTranslate(options);
61
+ await this.handleTranslate(initOptions);
67
62
  endCommand('Done!');
68
63
  });
69
64
  }
65
+ async handleStage(initOptions) {
66
+ const settings = await generateSettings(initOptions);
67
+ if (!settings.stageTranslations) {
68
+ // Update settings.stageTranslations to true
69
+ settings.stageTranslations = true;
70
+ await updateConfig({
71
+ configFilepath: settings.config,
72
+ stageTranslations: true,
73
+ });
74
+ }
75
+ await handleStage(initOptions, settings, this.library, true);
76
+ }
77
+ async handleTranslate(initOptions) {
78
+ const settings = await generateSettings(initOptions);
79
+ if (!settings.stageTranslations) {
80
+ const results = await handleStage(initOptions, settings, this.library, false);
81
+ if (results) {
82
+ await handleTranslate(initOptions, settings, results);
83
+ }
84
+ }
85
+ else {
86
+ await handleDownload(initOptions, settings);
87
+ }
88
+ await postProcessTranslations(settings);
89
+ }
70
90
  setupUploadCommand() {
71
91
  this.program
72
92
  .command('upload')
@@ -197,48 +217,6 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
197
217
  // Process all file types at once with a single call
198
218
  await upload(sourceFiles, placeholderPaths, transformPaths, dataFormat, settings);
199
219
  }
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
- // Process all file types at once with a single call
224
- await translateFiles(sourceFiles, placeholderPaths, transformPaths, dataFormat, settings);
225
- // Localize static urls (/docs -> /[locale]/docs)
226
- if (settings.experimentalLocalizeStaticUrls) {
227
- await localizeStaticUrls(settings);
228
- }
229
- // Localize static imports (/docs -> /[locale]/docs)
230
- if (settings.experimentalLocalizeStaticImports) {
231
- await localizeStaticImports(settings);
232
- }
233
- // Flatten json files into a single file
234
- if (settings.experimentalFlattenJsonFiles) {
235
- await flattenJsonFiles(settings);
236
- }
237
- // Copy files to the target locale
238
- if (settings.options?.copyFiles) {
239
- await copyFile(settings);
240
- }
241
- }
242
220
  async handleSetupReactCommand(options) {
243
221
  await handleSetupReactCommand(options);
244
222
  }
@@ -258,13 +236,14 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
258
236
  const usingCDN = isUsingGT
259
237
  ? await promptConfirm({
260
238
  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
261
- ? 'https://generaltranslation.com/docs/next/reference/local-tx'
262
- : '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.`,
239
+ ? 'https://generaltranslation.com/en/docs/next/guides/local-tx'
240
+ : '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.`,
263
241
  defaultValue: true,
264
242
  })
265
243
  : false;
266
244
  if (isUsingGT && !usingCDN) {
267
- logMessage(`To prevent translations from being published, please disable the project setting on the dashboard: ${chalk.cyan('https://dash.generaltranslation.com/settings/project')}`);
245
+ logMessage(`Make sure to add a loadTranslations function to your app configuration to correctly use local translations.
246
+ See https://generaltranslation.com/en/docs/next/guides/local-tx`);
268
247
  }
269
248
  // Ask where the translations are stored
270
249
  const translationsDir = isUsingGT && !usingCDN
@@ -313,6 +292,7 @@ See the docs for more information: https://generaltranslation.com/docs/react/tut
313
292
  defaultLocale,
314
293
  locales,
315
294
  files: Object.keys(files).length > 0 ? files : undefined,
295
+ publish: isUsingGT && usingCDN,
316
296
  });
317
297
  logSuccess(`Feel free to edit ${chalk.cyan(configFilepath)} to customize your translation setup. Docs: https://generaltranslation.com/docs/cli/reference/config`);
318
298
  // 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 { versionId } = filesTranslationResponse.data[TEMPLATE_FILE_ID];
92
+ if (versionId) {
93
+ await updateConfig({
94
+ configFilepath: settings.config,
95
+ _versionId: 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,54 @@
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)
39
+ if (settings.options?.experimentalLocalizeStaticUrls) {
40
+ await localizeStaticUrls(settings);
41
+ }
42
+ // Localize static imports (/docs -> /[locale]/docs)
43
+ if (settings.options?.experimentalLocalizeStaticImports) {
44
+ await localizeStaticImports(settings);
45
+ }
46
+ // Flatten json files into a single file
47
+ if (settings.options?.experimentalFlattenJsonFiles) {
48
+ await flattenJsonFiles(settings);
49
+ }
50
+ // Copy files to the target locale
51
+ if (settings.options?.copyFiles) {
52
+ await copyFile(settings);
53
+ }
54
+ }
@@ -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;