gt 2.22.4 → 2.23.0

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 (127) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/auth/errors.d.ts +1 -0
  3. package/dist/auth/errors.js +12 -2
  4. package/dist/auth/errors.js.map +1 -1
  5. package/dist/auth/interactiveLogin.d.ts +2 -0
  6. package/dist/auth/interactiveLogin.js +22 -0
  7. package/dist/auth/interactiveLogin.js.map +1 -0
  8. package/dist/auth/oauth.js +2 -2
  9. package/dist/auth/oauth.js.map +1 -1
  10. package/dist/cli/base.d.ts +22 -4
  11. package/dist/cli/base.js +328 -223
  12. package/dist/cli/base.js.map +1 -1
  13. package/dist/cli/commands/api.js +15 -16
  14. package/dist/cli/commands/api.js.map +1 -1
  15. package/dist/cli/node.js +1 -1
  16. package/dist/cli/node.js.map +1 -1
  17. package/dist/cli/react.js +1 -1
  18. package/dist/cli/react.js.map +1 -1
  19. package/dist/cli/vue.js +2 -2
  20. package/dist/cli/vue.js.map +1 -1
  21. package/dist/config/generateSettings.d.ts +2 -0
  22. package/dist/config/generateSettings.js +13 -6
  23. package/dist/config/generateSettings.js.map +1 -1
  24. package/dist/config/resolveConfig.js +0 -4
  25. package/dist/config/resolveConfig.js.map +1 -1
  26. package/dist/config/validateSettings.js +5 -4
  27. package/dist/config/validateSettings.js.map +1 -1
  28. package/dist/console/{inkLocaleData.d.ts → localeOptions.d.ts} +7 -2
  29. package/dist/console/{inkLocaleData.js → localeOptions.js} +3 -8
  30. package/dist/console/localeOptions.js.map +1 -0
  31. package/dist/console/logger.d.ts +7 -0
  32. package/dist/console/logger.js +48 -85
  33. package/dist/console/logger.js.map +1 -1
  34. package/dist/console/logging.d.ts +11 -1
  35. package/dist/console/logging.js +88 -100
  36. package/dist/console/logging.js.map +1 -1
  37. package/dist/formats/files/aggregateFiles.js +1 -1
  38. package/dist/formats/files/supportedFiles.d.ts +3 -0
  39. package/dist/formats/files/supportedFiles.js +10 -1
  40. package/dist/formats/files/supportedFiles.js.map +1 -1
  41. package/dist/formats/yaml/mergeYaml.js +1 -1
  42. package/dist/fs/config/loadConfig.d.ts +2 -0
  43. package/dist/fs/config/loadConfig.js +5 -1
  44. package/dist/fs/config/loadConfig.js.map +1 -1
  45. package/dist/fs/config/setupConfig.d.ts +18 -9
  46. package/dist/fs/config/setupConfig.js +51 -31
  47. package/dist/fs/config/setupConfig.js.map +1 -1
  48. package/dist/fs/createLoadTranslationsFile.d.ts +22 -1
  49. package/dist/fs/createLoadTranslationsFile.js +43 -20
  50. package/dist/fs/createLoadTranslationsFile.js.map +1 -1
  51. package/dist/generated/version.d.ts +1 -1
  52. package/dist/generated/version.js +1 -1
  53. package/dist/generated/version.js.map +1 -1
  54. package/dist/hooks/postProcess.d.ts +1 -2
  55. package/dist/hooks/postProcess.js +1 -1
  56. package/dist/hooks/postProcess.js.map +1 -1
  57. package/dist/locadex/setupFlow.d.ts +4 -1
  58. package/dist/locadex/setupFlow.js +5 -3
  59. package/dist/locadex/setupFlow.js.map +1 -1
  60. package/dist/react/jsx/utils/jsxParsing/addGTIdentifierToSyntaxTree.js +1 -1
  61. package/dist/react/jsx/utils/jsxParsing/parseJsx.js +1 -1
  62. package/dist/react/jsx/utils/parseString.js +1 -1
  63. package/dist/react/jsx/utils/stringParsing/derivation/index.js +1 -1
  64. package/dist/setup/buildTools/index.d.ts +44 -0
  65. package/dist/setup/buildTools/index.js +10 -0
  66. package/dist/setup/buildTools/index.js.map +1 -0
  67. package/dist/setup/buildTools/vite.d.ts +2 -0
  68. package/dist/setup/buildTools/vite.js +61 -0
  69. package/dist/setup/buildTools/vite.js.map +1 -0
  70. package/dist/setup/developmentCredentials.d.ts +40 -5
  71. package/dist/setup/developmentCredentials.js +119 -37
  72. package/dist/setup/developmentCredentials.js.map +1 -1
  73. package/dist/setup/instructions/base.md +1 -1
  74. package/dist/setup/onboarding.d.ts +131 -0
  75. package/dist/setup/onboarding.js +289 -0
  76. package/dist/setup/onboarding.js.map +1 -0
  77. package/dist/setup/setupTarget.d.ts +1 -0
  78. package/dist/setup/setupTarget.js +52 -0
  79. package/dist/setup/setupTarget.js.map +1 -0
  80. package/dist/setup/setupViteSPA.d.ts +32 -1
  81. package/dist/setup/setupViteSPA.js +110 -20
  82. package/dist/setup/setupViteSPA.js.map +1 -1
  83. package/dist/setup/userInput.d.ts +12 -3
  84. package/dist/setup/userInput.js +27 -11
  85. package/dist/setup/userInput.js.map +1 -1
  86. package/dist/setup/wizard.d.ts +19 -3
  87. package/dist/setup/wizard.js +119 -105
  88. package/dist/setup/wizard.js.map +1 -1
  89. package/dist/types/index.d.ts +2 -1
  90. package/dist/types/index.js +13 -1
  91. package/dist/types/index.js.map +1 -0
  92. package/dist/utils/credentials.d.ts +9 -6
  93. package/dist/utils/credentials.js +36 -22
  94. package/dist/utils/credentials.js.map +1 -1
  95. package/dist/utils/monorepoVersionCheck.d.ts +1 -1
  96. package/dist/utils/monorepoVersionCheck.js +5 -5
  97. package/dist/utils/monorepoVersionCheck.js.map +1 -1
  98. package/dist/utils/packageManager.js +13 -14
  99. package/dist/utils/packageManager.js.map +1 -1
  100. package/package.json +5 -9
  101. package/dist/console/inkFields.d.ts +0 -23
  102. package/dist/console/inkFields.js +0 -88
  103. package/dist/console/inkFields.js.map +0 -1
  104. package/dist/console/inkLayout.d.ts +0 -7
  105. package/dist/console/inkLayout.js +0 -140
  106. package/dist/console/inkLayout.js.map +0 -1
  107. package/dist/console/inkLocaleData.js.map +0 -1
  108. package/dist/console/inkPrompts.d.ts +0 -8
  109. package/dist/console/inkPrompts.js +0 -499
  110. package/dist/console/inkPrompts.js.map +0 -1
  111. package/dist/console/inkSession.d.ts +0 -3
  112. package/dist/console/inkSession.js +0 -42
  113. package/dist/console/inkSession.js.map +0 -1
  114. package/dist/console/inkTerminal.d.ts +0 -4
  115. package/dist/console/inkTerminal.js +0 -15
  116. package/dist/console/inkTerminal.js.map +0 -1
  117. package/dist/console/inkTheme.d.ts +0 -1
  118. package/dist/console/inkTheme.js +0 -6
  119. package/dist/console/inkTheme.js.map +0 -1
  120. package/dist/console/inkTypes.d.ts +0 -69
  121. package/dist/console/inkTypes.js +0 -1
  122. package/dist/console/inkUtils.d.ts +0 -24
  123. package/dist/console/inkUtils.js +0 -87
  124. package/dist/console/inkUtils.js.map +0 -1
  125. package/dist/console/terminalSession.d.ts +0 -5
  126. package/dist/console/terminalSession.js +0 -38
  127. package/dist/console/terminalSession.js.map +0 -1
package/dist/cli/base.js CHANGED
@@ -1,26 +1,28 @@
1
1
  import { logger } from "../console/logger.js";
2
2
  import { isPackageInstalled, searchForPackageJson } from "../utils/packageJson.js";
3
- import { DEFAULT_TRANSLATIONS_DIR, DEFAULT_VITE_TRANSLATIONS_DIR } from "../utils/constants.js";
3
+ import "../utils/constants.js";
4
4
  import { parseGlobPatterns } from "../console/promptParsing.js";
5
5
  import { INLINE_LIBRARIES } from "../types/libraries.js";
6
6
  import { displayHeader, exitSync, logErrorAndExit, promptConfirm, promptGlobPatterns, promptMultiSelect, promptSelect, promptText } from "../console/logging.js";
7
7
  import findFilepath from "../fs/findFilepath.js";
8
8
  import { lottieTranslateError } from "../console/index.js";
9
- import { loadConfig } from "../fs/config/loadConfig.js";
10
- import { FILE_EXT_TO_EXT_LABEL } from "../formats/files/supportedFiles.js";
11
- import { UserAuthError } from "../auth/errors.js";
12
- import { hasLogin, login, logout, whoAmI } from "../auth/oauth.js";
9
+ import { loadConfig, withJsonExtension } from "../fs/config/loadConfig.js";
10
+ import { FILE_EXT_TO_EXT_LABEL, SETUP_FILE_FORMATS, SUPPORTED_FILE_EXTENSIONS } from "../formats/files/supportedFiles.js";
11
+ import { resolveProjectId } from "../fs/utils.js";
12
+ import { UserAuthError, createUserAuthError } from "../auth/errors.js";
13
+ import { hasLogin, logout, whoAmI } from "../auth/oauth.js";
13
14
  import { resolveConfig } from "../config/resolveConfig.js";
14
15
  import { api } from "../utils/api.js";
15
16
  import { generateSettings } from "../config/generateSettings.js";
16
- import { createOrUpdateConfig } from "../fs/config/setupConfig.js";
17
- import { getPackageManager } from "../utils/packageManager.js";
18
- import { installPackage } from "../utils/installPackage.js";
19
- import { getFrameworkDisplayName, getReactFrameworkLibrary } from "../setup/frameworkUtils.js";
20
- import { handleSetupReactCommand } from "../setup/wizard.js";
17
+ import { createOrUpdateConfig, mergeSetupConfig } from "../fs/config/setupConfig.js";
18
+ import { executeReactSetup, resolveReactSetup } from "../setup/wizard.js";
21
19
  import { getDesiredLocales } from "../setup/userInput.js";
22
- import { areCredentialsSet } from "../utils/credentials.js";
23
- import { provisionDevelopmentCredentials } from "../setup/developmentCredentials.js";
20
+ import { areCredentialsSet, getDevelopmentEnvNames, inspectCredentialsEnvFile } from "../utils/credentials.js";
21
+ import { loginInteractively } from "../auth/interactiveLogin.js";
22
+ import { getBuildToolSetup } from "../setup/buildTools/index.js";
23
+ import { INIT_SOURCE_HELP, OnboardingError, asRecord, attachConfigureFlags, attachInitFlags, describeDefaults, displaySetupHeader, getConfiguredFramework, installWithProgress, parseFilePatterns, readSetupConfig, reportJsonExitsBeforeOnboarding, resolvePackageManager, runOnboarding, setupConfigPath, validateSetupPattern } from "../setup/onboarding.js";
24
+ import { checkDevelopmentProjectInputs, productionRuntimeKeyGuidance, provisionDevelopmentCredentials, resolveDevelopmentProject, signInForSetup } from "../setup/developmentCredentials.js";
25
+ import { exitIfUnsupportedSetupTarget } from "../setup/setupTarget.js";
24
26
  import { clearWarnings } from "../state/translateWarnings.js";
25
27
  import { hasValidCredentials, hasValidServiceLocales } from "./commands/utils/validation.js";
26
28
  import { upload } from "./commands/upload.js";
@@ -32,7 +34,7 @@ import { clearDownloaded, getNeedsPostprocessing } from "../state/recentDownload
32
34
  import { handleDownload } from "./commands/download.js";
33
35
  import { handleTranslate, postProcessTranslations } from "./commands/translate.js";
34
36
  import { displayTranslateSummary } from "../console/displayTranslateSummary.js";
35
- import { createLoadTranslationsFile } from "../fs/createLoadTranslationsFile.js";
37
+ import { createLoadTranslationsFile, findLoadTranslationsFiles } from "../fs/createLoadTranslationsFile.js";
36
38
  import { saveLocalEdits } from "../api/saveLocalEdits.js";
37
39
  import processSharedStaticAssets, { mirrorAssetsToLocales } from "../utils/sharedStaticAssets.js";
38
40
  import { setupLocadex } from "../locadex/setupFlow.js";
@@ -42,11 +44,9 @@ import { splitMintlifyLanguageRefs } from "../utils/splitMintlifyLanguageRefs.js
42
44
  import { runMergeDriver } from "../git/mergeDrivers.js";
43
45
  import { setupGitMergeDrivers } from "../git/setupMergeDrivers.js";
44
46
  import { warnReactPackageCompatibility } from "../utils/reactPackageCompatibility.js";
45
- import { setupViteSPA } from "../setup/setupViteSPA.js";
46
47
  import { handleApiCommand } from "./commands/api.js";
47
48
  import chalk from "chalk";
48
49
  import path from "node:path";
49
- import fs from "node:fs";
50
50
  import { createDiagnosticMessage, formatDiagnosticErrorDetails } from "generaltranslation/diagnostics";
51
51
  import { ProjectApiKeyPermission } from "generaltranslation/api";
52
52
  import { manifestDirectlyDeclaresGTVue } from "@generaltranslation/vue-extractor/integration";
@@ -61,13 +61,6 @@ const ID_COMPATIBILITY_WARNING_COMMANDS = new Set([
61
61
  "translate",
62
62
  "validate"
63
63
  ]);
64
- const workspaceRootSetupError = createDiagnosticMessage({
65
- source: "gt",
66
- severity: "Error",
67
- whatHappened: "The setup wizard cannot run from a monorepo workspace root",
68
- why: "GT must be configured in the specific app you want to localize",
69
- fix: "Change to that app's directory and rerun `npx gt@latest`"
70
- });
71
64
  function createProjectCommandError(whatHappened, error) {
72
65
  return createDiagnosticMessage({
73
66
  source: "gt",
@@ -85,42 +78,14 @@ function parseApiKeyName(value) {
85
78
  if (!name) throw new InvalidArgumentError(emptyApiKeyNameError);
86
79
  return name;
87
80
  }
88
- const electronSetupError = createDiagnosticMessage({
89
- source: "gt",
90
- severity: "Error",
91
- whatHappened: "The automatic setup wizard is not ready for Electron applications",
92
- docsUrl: "https://generaltranslation.com/docs/react"
93
- });
94
- /** .env.local never reaches production; the runtime key there is set on the host. */
95
- function productionRuntimeKeyGuidance(dashboardUrl) {
96
- return `${chalk.dim("For runtime translation in production, create an API key in the dashboard")} ${chalk.cyan(dashboardUrl)} ${chalk.dim("and set GT_API_KEY and GT_PROJECT_ID in your hosting environment.")}`;
97
- }
98
- async function loginInteractively(baseUrl, useBrowser = true) {
99
- await login({
100
- baseUrl,
101
- noBrowser: !useBrowser,
102
- onDeviceCode: ({ userCode, verificationUri, verificationUriComplete }) => {
103
- logger.message(`Visit:\n\n${chalk.cyan(verificationUriComplete ?? verificationUri)}\n\nThen ${verificationUriComplete ? "confirm" : "enter"} the code ${chalk.bold(userCode)}.\nWaiting for authentication...`);
104
- },
105
- onAuthorizationUrl: (url) => {
106
- logger.message(`Opening your browser to sign in. If it does not open, visit:\n${chalk.cyan(url)}`);
107
- }
108
- });
109
- }
110
- function createUserAuthError(whatHappened, error) {
111
- if (error instanceof UserAuthError) return error.message;
112
- return createDiagnosticMessage({
113
- source: "gt",
114
- severity: "Error",
115
- whatHappened,
116
- details: formatDiagnosticErrorDetails(error),
117
- fix: "Run `gt login` and try again"
118
- });
119
- }
120
- async function exitIfUnsupportedSetupTarget() {
121
- const packageJson = await searchForPackageJson();
122
- if (packageJson && isPackageInstalled("electron", packageJson, false, true)) logErrorAndExit(electronSetupError);
123
- if (fs.existsSync(path.join(process.cwd(), "pnpm-workspace.yaml")) || packageJson?.workspaces) logErrorAndExit(workspaceRootSetupError);
81
+ /** Credential failures keep their own diagnostic or get the setup context. */
82
+ async function withCredentialsError(run) {
83
+ try {
84
+ return await run();
85
+ } catch (error) {
86
+ if (error instanceof OnboardingError) throw error;
87
+ throw new Error(error instanceof UserAuthError ? error.message : createProjectCommandError("Failed to set up the development credentials", error));
88
+ }
124
89
  }
125
90
  var BaseCLI = class {
126
91
  library;
@@ -133,8 +98,9 @@ var BaseCLI = class {
133
98
  this.program.option("--skip-version-check", "Skip the monorepo GT package version consistency check");
134
99
  this.program.option("--suppress-id-compatibility-warning", "Suppress the React package ID compatibility warning");
135
100
  this.program.option("-q, --quiet", "Suppress informational output; only warnings and errors are shown");
136
- this.program.hook("preAction", (_thisCommand, actionCommand) => {
137
- logger.setConsoleOutput(actionCommand.parent?.name() === "api-key" ? "stderr" : "stdout");
101
+ this.program.hook("preAction", (thisCommand, actionCommand) => {
102
+ logger.setConsoleOutput(actionCommand.parent?.name() === "api-key" || actionCommand.opts().json ? "stderr" : "stdout");
103
+ if (actionCommand.opts().json && actionCommand.parent === thisCommand && ["init", "configure"].includes(actionCommand.name())) reportJsonExitsBeforeOnboarding(actionCommand.name());
138
104
  });
139
105
  this.program.hook("preAction", () => {
140
106
  logger.setQuiet(Boolean(this.program.opts().quiet));
@@ -360,10 +326,10 @@ var BaseCLI = class {
360
326
  });
361
327
  }
362
328
  setupUserAuthCommands() {
363
- this.program.command("login").description("Sign in to your General Translation account").option("--no-browser", "Do not open a browser; show a sign-in URL to use on any device instead").action(async (options) => {
329
+ this.program.command("login").description("Sign in to your General Translation account").option("--no-browser", "Do not open a browser; show a sign-in URL to use on any device instead").option("-c, --config <path>", "Filepath to config file, by default gt.config.json").action(async (options) => {
364
330
  displayHeader("Signing in to General Translation...");
365
331
  try {
366
- const baseUrl = resolveConfig(process.cwd())?.config.baseUrl;
332
+ const baseUrl = (options.config ? loadConfig(withJsonExtension(options.config)) : resolveConfig(process.cwd())?.config)?.baseUrl;
367
333
  await loginInteractively(typeof baseUrl === "string" ? baseUrl : void 0, options.browser);
368
334
  logger.endCommand("You are now signed in.");
369
335
  } catch (error) {
@@ -388,61 +354,87 @@ var BaseCLI = class {
388
354
  });
389
355
  }
390
356
  setupInitCommand() {
391
- this.program.command("init").description("Run the setup wizard to configure your project for General Translation").option("--src <paths...>", "Space-separated list of glob patterns containing the app's source code, by default 'src/**/*.{js,jsx,ts,tsx}' 'app/**/*.{js,jsx,ts,tsx}' 'pages/**/*.{js,jsx,ts,tsx}' 'components/**/*.{js,jsx,ts,tsx}'").option("-c, --config <path>", "Filepath to config file, by default gt.config.json", findFilepath(["gt.config.json"])).action(async (options) => {
357
+ attachInitFlags(this.program.command("init").description("Run the setup wizard to configure your project for General Translation. Flags answer its questions; unanswered ones are asked, or listed as missing with --no-interactive"), INIT_SOURCE_HELP).action((options) => this.handleInitWizard(options));
358
+ }
359
+ async handleInitWizard(options) {
360
+ await runOnboarding("init", options, async (session) => {
392
361
  await exitIfUnsupportedSetupTarget();
393
- const settings = await generateSettings(options);
394
- displayHeader("Running setup wizard...");
395
- const framework = await detectFramework();
396
- if (await (async () => {
397
- let useAgentMessage;
398
- if (framework.name === "mintlify") useAgentMessage = `Mintlify project detected. Would you like to connect to GitHub so that the Locadex AI Agent can translate your project automatically?`;
399
- if (framework.name === "next-app") useAgentMessage = `Next.js App Router detected. Would you like to connect to GitHub so that the Locadex AI Agent can set up your project automatically?`;
400
- if (useAgentMessage) return await promptConfirm({
401
- message: useAgentMessage,
402
- defaultValue: false
403
- });
404
- return false;
405
- })()) {
406
- await setupLocadex(settings);
407
- logger.endCommand("Once installed, Locadex will open a PR to your repository. See the docs for more information: https://generaltranslation.com/docs/locadex");
408
- } else {
409
- const frameworkDisplayName = framework.type === "react" ? getFrameworkDisplayName(framework) : null;
410
- const library = framework.type === "react" ? getReactFrameworkLibrary(framework) : null;
411
- const defaultTranslationsDir = framework.name === "vite" ? DEFAULT_VITE_TRANSLATIONS_DIR : DEFAULT_TRANSLATIONS_DIR;
412
- const defaultsDescription = framework.name === "vite" ? `${library} & initializeGTSPA, ${frameworkDisplayName}, Files saved locally in ${defaultTranslationsDir}` : framework.type === "react" ? `${library} & GTProvider, ${frameworkDisplayName}, Files saved locally in ${defaultTranslationsDir}` : `Files saved locally in ${defaultTranslationsDir}`;
413
- const useDefaults = await promptConfirm({
414
- message: `Would you like to use the recommended General Translation defaults? ${chalk.dim(`(${defaultsDescription})`)}`,
415
- defaultValue: true
416
- });
417
- let ranReactSetup = false;
418
- if (framework.type === "react") {
419
- if (useDefaults ? true : await promptConfirm({
420
- message: framework.name === "vite" ? `Would you like to install ${library} and configure initializeGTSPA? See the docs for more information: https://generaltranslation.com/docs/react/tutorials/quickstart` : `Would you like to install ${library} and add the GTProvider? See the docs for more information: https://generaltranslation.com/docs/react/tutorials/quickstart`,
421
- defaultValue: true
422
- })) {
423
- logger.info(`${chalk.yellow("[EXPERIMENTAL]")} Configuring project...`);
424
- await handleSetupReactCommand(options, framework, useDefaults);
425
- logger.endCommand(`Done! Since this wizard is experimental, please review the changes and make modifications as needed.
426
- \nNext step: start internationalizing! See the docs for more information: https://generaltranslation.com/docs/react/tutorials/quickstart`);
427
- ranReactSetup = true;
428
- }
362
+ displaySetupHeader(session, "Running setup wizard...");
363
+ const detected = await detectFramework();
364
+ const reactDetected = detected.type === "react" ? detected : void 0;
365
+ if (options.framework && !reactDetected) session.reject("--framework applies only to projects detected as React applications");
366
+ const configFilepath = setupConfigPath(options);
367
+ const configured = getConfiguredFramework(readSetupConfig(configFilepath));
368
+ const configuredReact = configured !== "mintlify" ? configured : void 0;
369
+ if (options.defaults !== void 0) session.defaults = options.defaults;
370
+ const gateFramework = options.framework ?? configured ?? detected.name;
371
+ if (gateFramework === "mintlify" || gateFramework === "next-app") {
372
+ if (await session.answer("--locadex", {
373
+ explicit: options.locadex,
374
+ recommended: false,
375
+ ask: () => promptConfirm({
376
+ message: gateFramework === "mintlify" ? `Mintlify project detected. Would you like to connect to GitHub so that the Locadex AI Agent can translate your project automatically?` : `Next.js App Router detected. Would you like to connect to GitHub so that the Locadex AI Agent can set up your project automatically?`,
377
+ defaultValue: false
378
+ })
379
+ })) {
380
+ session.assertResolved();
381
+ const url = await setupLocadex(await generateSettings({ config: options.config }), { openBrowser: session.interactive });
382
+ session.emit({
383
+ type: "handoff",
384
+ url,
385
+ reason: "locadex"
386
+ });
387
+ logger.endCommand("Once installed, Locadex will open a PR to your repository. See the docs for more information: https://generaltranslation.com/docs/locadex");
388
+ return {
389
+ outcome: "needs_human_action",
390
+ url
391
+ };
429
392
  }
430
- if (ranReactSetup) logger.startCommand("Setting up project config...");
431
- await this.handleInitCommand(ranReactSetup, useDefaults, framework.name === "vite", options, framework.name);
432
- logger.endCommand("Done! Check out our docs for more information on how to use General Translation: https://generaltranslation.com/docs");
433
- }
393
+ } else if (options.locadex) session.reject("--locadex applies only to Mintlify and Next.js App Router projects");
394
+ const shownFramework = reactDetected && {
395
+ ...reactDetected,
396
+ name: options.framework ?? configuredReact ?? reactDetected.name
397
+ };
398
+ session.defaults = await session.answer("--defaults", {
399
+ explicit: options.defaults,
400
+ fallback: false,
401
+ ask: () => promptConfirm({
402
+ message: `Would you like to use the recommended General Translation defaults? ${chalk.dim(`(${describeDefaults(shownFramework)})`)}`,
403
+ defaultValue: true
404
+ })
405
+ }) === true;
406
+ const reactSetup = reactDetected ? await resolveReactSetup(session, options, reactDetected, configuredReact) : void 0;
407
+ const framework = reactSetup?.framework ?? options.framework ?? configured ?? detected.name;
408
+ await this.handleInitCommand(session, options, {
409
+ configFilepath,
410
+ framework,
411
+ saveFramework: Boolean(reactSetup || options.framework),
412
+ reactSetup,
413
+ keepAppSource: !reactSetup
414
+ });
415
+ logger.endCommand("Done! Check out our docs for more information on how to use General Translation: https://generaltranslation.com/docs");
416
+ return { outcome: "success" };
434
417
  });
435
418
  }
436
419
  setupConfigureCommand() {
437
- this.program.command("configure").description("Configure your project for General Translation. This will create a gt.config.json file in your codebase.").action(() => this.handleConfigureCommand());
420
+ attachConfigureFlags(this.program.command("configure").description("Configure your project for General Translation. This will create a gt.config.json file in your codebase. Flags answer its questions; unanswered ones are asked, or listed as missing with --no-interactive"), INIT_SOURCE_HELP).action((options) => this.handleConfigureCommand(options));
438
421
  }
439
- async handleConfigureCommand(options) {
440
- await exitIfUnsupportedSetupTarget();
441
- displayHeader("Configuring project...");
442
- logger.info("Welcome! This tool will help you configure your gt.config.json file. See the docs: https://generaltranslation.com/docs/cli/reference/config for more information.");
443
- const framework = await detectFramework();
444
- await this.handleInitCommand(false, false, framework.name === "vite", options);
445
- logger.endCommand("Done! Make sure you have an API key and project ID to use General Translation. Get them on the dashboard: https://generaltranslation.com/dashboard");
422
+ async handleConfigureCommand(options, command = "configure") {
423
+ await runOnboarding(command, options, async (session) => {
424
+ await exitIfUnsupportedSetupTarget();
425
+ displaySetupHeader(session, "Configuring project...");
426
+ logger.info("Welcome! This tool will help you configure your gt.config.json file. See the docs: https://generaltranslation.com/docs/cli/reference/config for more information.");
427
+ session.defaults = options.defaults ?? false;
428
+ const detected = await detectFramework();
429
+ const configFilepath = setupConfigPath(options);
430
+ const framework = getConfiguredFramework(readSetupConfig(configFilepath)) ?? detected.name;
431
+ await this.handleInitCommand(session, options, {
432
+ configFilepath,
433
+ framework
434
+ });
435
+ logger.endCommand("Done! Make sure you have an API key and project ID to use General Translation. Get them on the dashboard: https://generaltranslation.com/dashboard");
436
+ return { outcome: "success" };
437
+ });
446
438
  }
447
439
  async handleUploadCommand(settings) {
448
440
  if (!settings.files) return;
@@ -466,8 +458,8 @@ var BaseCLI = class {
466
458
  return !runtimeSetup.hasVueRuntime;
467
459
  }
468
460
  /** Returns whether setup should generate a local runtime loader. */
469
- shouldGenerateLocalTranslationLoader(isVite, runtimeSetup) {
470
- if (isVite) return false;
461
+ shouldGenerateLocalTranslationLoader(buildToolOwnsLoader, runtimeSetup) {
462
+ if (buildToolOwnsLoader) return false;
471
463
  if (!runtimeSetup.hasVueRuntime) return true;
472
464
  return runtimeSetup.hasOtherInlineRuntime || runtimeSetup.ranReactSetup;
473
465
  }
@@ -482,21 +474,35 @@ Configure createGT({ loadTranslations }) to load files from that directory.
482
474
  See https://www.npmjs.com/package/gt-vue`);
483
475
  return guidance.length > 0 ? guidance.join("\n") : void 0;
484
476
  }
485
- async handleInitCommand(ranReactSetup, useDefaults = false, isVite = false, options, framework) {
486
- const configFilepath = options?.config || (!isVite && fs.existsSync("src/gt.config.json") ? "src/gt.config.json" : "gt.config.json");
487
- const { defaultLocale, locales } = await getDesiredLocales(loadConfig(configFilepath));
488
- const packageJson = await searchForPackageJson();
489
- const runtimeSetup = packageJson ? this.getInlineRuntimeSetup(packageJson, ranReactSetup) : {
477
+ /**
478
+ * Resolves every configuration answer and validates the resulting config,
479
+ * checks the project and signs in before the first change, then applies
480
+ * the React setup, gt.config.json, installs and development credentials.
481
+ */
482
+ async handleInitCommand(session, options, setup) {
483
+ const { configFilepath, reactSetup } = setup;
484
+ const buildTool = getBuildToolSetup(setup.framework);
485
+ const cwd = process.cwd();
486
+ const existingConfig = readSetupConfig(configFilepath);
487
+ const { defaultLocale, locales } = await getDesiredLocales(session, existingConfig, options);
488
+ const packageJson = reactSetup?.packageJson ?? await searchForPackageJson();
489
+ const runtimeSetup = packageJson ? this.getInlineRuntimeSetup(packageJson, Boolean(reactSetup)) : {
490
490
  hasOtherInlineRuntime: false,
491
491
  hasVueRuntime: false,
492
- ranReactSetup
492
+ ranReactSetup: false
493
493
  };
494
494
  const isUsingGT = runtimeSetup.ranReactSetup || runtimeSetup.hasOtherInlineRuntime || runtimeSetup.hasVueRuntime;
495
- const usingCDN = await (async () => {
496
- if (!isUsingGT) return false;
497
- if (useDefaults) return false;
498
- if (!this.supportsCDNStorage(runtimeSetup)) return false;
499
- return await promptSelect({
495
+ const supportsCDN = this.supportsCDNStorage(runtimeSetup);
496
+ const existingFiles = asRecord(existingConfig.files) ?? {};
497
+ const existingGtOutput = asRecord(existingFiles.gt)?.output;
498
+ const configuredOutput = typeof existingGtOutput === "string" ? existingGtOutput : void 0;
499
+ const configuredStorage = configuredOutput ? "local" : existingConfig.publish === true ? "cdn" : void 0;
500
+ if (isUsingGT && !supportsCDN && options.storage === "cdn") session.reject("gt-vue cannot load translations from the CDN; use --storage local");
501
+ const storage = !isUsingGT ? void 0 : !supportsCDN ? "local" : await session.answer("--storage", {
502
+ explicit: options.storage,
503
+ configured: configuredStorage,
504
+ recommended: "local",
505
+ ask: () => promptSelect({
500
506
  message: `Would you like to save translation files locally or use the General Translation CDN to store them?`,
501
507
  options: [{
502
508
  value: "local",
@@ -506,116 +512,215 @@ See https://www.npmjs.com/package/gt-vue`);
506
512
  label: "Use CDN"
507
513
  }],
508
514
  defaultValue: "local"
509
- }) === "cdn";
510
- })();
511
- const defaultTranslationsDir = isVite ? DEFAULT_VITE_TRANSLATIONS_DIR : DEFAULT_TRANSLATIONS_DIR;
512
- const finalTranslationsDir = (isUsingGT && !usingCDN ? useDefaults ? defaultTranslationsDir : await promptText({
513
- message: "What is the path to the directory where you would like to store your translation files?",
514
- defaultValue: defaultTranslationsDir
515
- }) : null)?.trim() || defaultTranslationsDir;
516
- if (isUsingGT && !usingCDN) {
517
- const generatedLoader = this.shouldGenerateLocalTranslationLoader(isVite, runtimeSetup);
518
- if (generatedLoader) await createLoadTranslationsFile(process.cwd(), finalTranslationsDir, locales);
519
- const guidance = this.getLocalTranslationGuidance({
520
- generatedLoader,
521
- runtimeSetup,
522
- translationsDir: finalTranslationsDir
523
- });
524
- if (guidance) logger.message(guidance);
515
+ })
516
+ });
517
+ const defaultTranslationsDir = buildTool?.defaultTranslationsDir ?? "./public/_gt";
518
+ const outputMatch = configuredOutput?.match(/^(?:(.+)[\\/])?\[locale\]\.json$/);
519
+ const configuredTranslationsDir = outputMatch ? outputMatch[1] ?? "." : void 0;
520
+ const translationsDir = storage === "local" ? await session.answer("--translations-dir", {
521
+ explicit: options.translationsDir?.trim() || void 0,
522
+ configured: configuredTranslationsDir,
523
+ recommended: defaultTranslationsDir,
524
+ ask: async () => (await promptText({
525
+ message: "What is the path to the directory where you would like to store your translation files?",
526
+ defaultValue: defaultTranslationsDir
527
+ })).trim() || defaultTranslationsDir
528
+ }) : void 0;
529
+ const filePatterns = parseFilePatterns(session, options.filePatterns);
530
+ const selectedFormats = options.fileFormats?.filter((format) => format !== "none");
531
+ if (options.fileFormats?.includes("none") && (selectedFormats?.length ?? 0) > 0) session.reject("--file-formats none cannot be combined with formats");
532
+ const configuredFormats = SETUP_FILE_FORMATS.filter((format) => format in existingFiles);
533
+ const otherFormats = SUPPORTED_FILE_EXTENSIONS.filter((format) => format in existingFiles && !SETUP_FILE_FORMATS.includes(format));
534
+ const hasOtherFormats = otherFormats.length > 0;
535
+ if (options.fileFormats && hasOtherFormats) {
536
+ const kept = otherFormats.map((format) => `files.${format}`).join(", ");
537
+ logger.warn(createDiagnosticMessage({
538
+ source: "gt",
539
+ severity: "Warning",
540
+ whatHappened: `${kept} stays in ${configFilepath}`,
541
+ why: "--file-formats only changes the formats setup offers",
542
+ fix: `Remove ${kept} from ${configFilepath} to stop translating those files`
543
+ }));
525
544
  }
526
- const message = !isUsingGT ? "What is the format of your language resource files? Select as many as applicable.\nAdditionally, you can translate any other files you have in your project." : `Do you have any additional files in this project to translate? For example, Markdown files for docs. ${chalk.dim("(To continue without selecting press Enter)")}`;
527
- const fileExtensions = useDefaults && isUsingGT ? [] : await promptMultiSelect({
528
- message,
529
- options: [
530
- {
531
- value: "json",
532
- label: FILE_EXT_TO_EXT_LABEL.json
533
- },
534
- {
535
- value: "md",
536
- label: FILE_EXT_TO_EXT_LABEL.md
537
- },
538
- {
539
- value: "mdx",
540
- label: FILE_EXT_TO_EXT_LABEL.mdx
541
- },
542
- {
543
- value: "ts",
544
- label: FILE_EXT_TO_EXT_LABEL.ts
545
- },
546
- {
547
- value: "js",
548
- label: FILE_EXT_TO_EXT_LABEL.js
549
- },
550
- {
551
- value: "yaml",
552
- label: FILE_EXT_TO_EXT_LABEL.yaml
553
- }
554
- ],
555
- required: !isUsingGT
545
+ const fileFormats = await session.answer("--file-formats", {
546
+ explicit: selectedFormats ?? (filePatterns.size > 0 ? [...filePatterns.keys()] : void 0),
547
+ configured: configuredFormats.length > 0 || hasOtherFormats ? configuredFormats : void 0,
548
+ recommended: isUsingGT ? [] : void 0,
549
+ ask: () => promptMultiSelect({
550
+ message: !isUsingGT ? "What is the format of your language resource files? Select as many as applicable.\nAdditionally, you can translate any other files you have in your project." : `Do you have any additional files in this project to translate? For example, Markdown files for docs. ${chalk.dim("(To continue without selecting press Enter)")}`,
551
+ options: SETUP_FILE_FORMATS.map((format) => ({
552
+ value: format,
553
+ label: FILE_EXT_TO_EXT_LABEL[format]
554
+ })),
555
+ required: !isUsingGT
556
+ })
556
557
  });
558
+ if (fileFormats && fileFormats.length === 0 && !isUsingGT && !hasOtherFormats) session.reject("No GT runtime is installed, so select at least one file format to translate");
559
+ for (const format of filePatterns.keys()) if (fileFormats && !fileFormats.includes(format)) session.reject(`--file-patterns ${format}= needs ${format} in --file-formats`);
557
560
  const files = {};
558
- for (const fileExtension of fileExtensions) {
559
- const label = FILE_EXT_TO_EXT_LABEL[fileExtension];
560
- files[fileExtension] = { include: parseGlobPatterns(await promptGlobPatterns({
561
- label,
562
- message: `${chalk.cyan(FILE_EXT_TO_EXT_LABEL[fileExtension])}: Enter a space-separated list of glob patterns matching the location of the ${FILE_EXT_TO_EXT_LABEL[fileExtension]} files you would like to translate.\nMake sure to include [locale] in the patterns.\nSee https://generaltranslation.com/docs/cli/reference/config#include for more information.`,
563
- defaultValue: `./**/[locale]/*.${fileExtension}`
564
- })) };
561
+ for (const fileExtension of fileFormats ?? []) {
562
+ const existingFormat = asRecord(existingFiles[fileExtension]);
563
+ const configuredInclude = Array.isArray(existingFormat?.include) ? existingFormat.include : void 0;
564
+ const include = await session.answer(`--file-patterns ${fileExtension}=<glob>`, {
565
+ explicit: filePatterns.get(fileExtension),
566
+ configured: configuredInclude,
567
+ recommended: [`./**/[locale]/*.${fileExtension}`],
568
+ ask: async () => parseGlobPatterns(await promptGlobPatterns({
569
+ label: FILE_EXT_TO_EXT_LABEL[fileExtension],
570
+ message: `${chalk.cyan(FILE_EXT_TO_EXT_LABEL[fileExtension])}: Enter a space-separated list of glob patterns matching the location of the ${FILE_EXT_TO_EXT_LABEL[fileExtension]} files you would like to translate.\nMake sure to include [locale] in the patterns.\nSee https://generaltranslation.com/docs/cli/reference/config#include for more information.`,
571
+ defaultValue: `./**/[locale]/*.${fileExtension}`,
572
+ validate: (value) => parseGlobPatterns(value).map(validateSetupPattern).find((problem) => problem !== true) ?? true
573
+ }))
574
+ });
575
+ if (include && include !== configuredInclude) files[fileExtension] = {
576
+ ...existingFormat,
577
+ include
578
+ };
565
579
  }
566
- if (isUsingGT && !usingCDN) files.gt = { output: path.join(finalTranslationsDir, `[locale].json`) };
567
- await createOrUpdateConfig(configFilepath, {
580
+ if (translationsDir && translationsDir !== configuredTranslationsDir) files.gt = { output: path.join(translationsDir, `[locale].json`) };
581
+ const envFramework = setup.framework ?? getConfiguredFramework(existingConfig);
582
+ const projectId = options.projectId || (typeof existingConfig.projectId === "string" ? existingConfig.projectId : void 0) || process.env[getDevelopmentEnvNames(envFramework).projectId];
583
+ const configUpdate = {
568
584
  defaultLocale,
569
585
  locales,
570
- src: options?.src,
586
+ src: options.src,
571
587
  files: Object.keys(files).length > 0 ? files : void 0,
572
- framework: isVite ? "vite" : void 0,
573
- publish: isUsingGT && usingCDN,
574
- clearPublish: runtimeSetup.hasVueRuntime && !usingCDN
575
- });
576
- logger.success(`Edit ${chalk.cyan(configFilepath)} to customize your translation setup. Docs: https://generaltranslation.com/docs/cli/reference/config`);
577
- if (ranReactSetup && isVite) await setupViteSPA({
578
- appDirectory: process.cwd(),
579
- configFilepath,
580
- defaultLocale,
581
- locales,
582
- translationsDir: usingCDN ? void 0 : finalTranslationsDir
583
- });
584
- if (!(packageJson ? isPackageInstalled("gt", packageJson, true, true) : true) && !(isUsingGT && isVite)) {
585
- const packageManager = await getPackageManager();
586
- const spinner = logger.createSpinner();
587
- spinner.start(`Installing gt as a dev dependency with ${packageManager.name}...`);
588
- await installPackage("gt", packageManager, true);
589
- spinner.stop(chalk.green("Installed gt."));
588
+ framework: setup.saveFramework && setup.framework || buildTool?.framework,
589
+ publish: storage === "cdn",
590
+ clearPublish: storage === "local" && (options.storage === "local" || configuredStorage !== "local" || !supportsCDN),
591
+ clearGtOutput: storage === "cdn" && configuredOutput !== void 0,
592
+ removeFiles: selectedFormats ? configuredFormats.filter((format) => !selectedFormats.includes(format)) : void 0
593
+ };
594
+ session.assertResolved();
595
+ const { projectId: _writtenProjectId, ...effectiveConfig } = mergeSetupConfig(existingConfig, configUpdate);
596
+ const settings = await generateSettings({
597
+ config: configFilepath,
598
+ ...projectId && { projectId }
599
+ }, cwd, { resolvedConfig: effectiveConfig });
600
+ const runtimeProjectId = process.env[getDevelopmentEnvNames(envFramework).projectId] ?? resolveProjectId();
601
+ const runtimeProjectMatches = runtimeProjectId === void 0 || runtimeProjectId === settings.projectId;
602
+ const credentialsSet = runtimeProjectMatches && areCredentialsSet(settings, envFramework);
603
+ const credentialsOption = storage === "local" && buildTool?.devCredentialsOption || "--dev-credentials";
604
+ const provision = !credentialsSet && (credentialsOption === "--live-translations" ? await session.answer("--live-translations", {
605
+ explicit: options.liveTranslations ?? options.devCredentials,
606
+ recommended: false,
607
+ ask: () => promptConfirm({
608
+ message: "Would you like to set up live development translations? This requires signing in or an API key.",
609
+ defaultValue: false
610
+ })
611
+ }) : await session.answer("--dev-credentials", {
612
+ explicit: options.devCredentials,
613
+ ask: () => promptConfirm({
614
+ message: "Would you like to set up a project ID and hot-reload key in .env.local?",
615
+ defaultValue: true
616
+ })
617
+ })) === true;
618
+ if (!runtimeProjectMatches && !provision) session.reject(`The runtime credentials in the environment belong to project ${runtimeProjectId}, not ${settings.projectId}; pass --dev-credentials to replace them in .env.local`);
619
+ if (options.projectId && (existingConfig.projectId || !provision)) configUpdate.projectId = options.projectId;
620
+ if (provision) {
621
+ checkDevelopmentProjectInputs(session, settings, options, options.projectId ? "--project-id" : typeof existingConfig.projectId === "string" && existingConfig.projectId ? configFilepath : "the environment");
622
+ await withCredentialsError(() => inspectCredentialsEnvFile(cwd, { framework: envFramework }));
590
623
  }
591
- const localVite = isVite && isUsingGT && !usingCDN;
592
- const enableLiveTranslations = localVite && await promptConfirm({
593
- message: "Would you like to set up live development translations? This requires signing in or an API key.",
594
- defaultValue: false
595
- });
596
- if (!localVite || enableLiveTranslations) {
597
- const settings = await generateSettings({ config: configFilepath });
598
- const envFramework = framework ?? (isVite ? "vite" : settings.framework);
599
- if (!settings.apiKey && !await hasLogin({ baseUrl: settings.baseUrl })) try {
600
- await loginInteractively(settings.baseUrl);
601
- logger.message("You are now signed in.");
602
- } catch (error) {
603
- logErrorAndExit(createUserAuthError("Sign in failed", error));
624
+ if (reactSetup && buildTool) await buildTool.preflight(cwd);
625
+ const installGT = packageJson !== null && !isPackageInstalled("gt", packageJson, true, true) && !buildTool?.skipsGTInstall(isUsingGT);
626
+ const packageManager = reactSetup?.install || installGT ? await resolvePackageManager(session, options.packageManager) : void 0;
627
+ session.assertResolved();
628
+ if (provision && !settings.apiKey && !await hasLogin({ baseUrl: settings.baseUrl })) await signInForSetup(session, settings.baseUrl);
629
+ const project = provision ? await withCredentialsError(() => resolveDevelopmentProject(session, settings, options, cwd)) : void 0;
630
+ session.assertResolved();
631
+ const resolvedLocales = locales;
632
+ if (reactSetup) {
633
+ logger.info(`${chalk.yellow("[EXPERIMENTAL]")} Configuring project...`);
634
+ if (reactSetup.install && packageManager) await installWithProgress(session, reactSetup.install, packageManager, false);
635
+ await executeReactSetup(session, reactSetup, options);
636
+ logger.endCommand(`Done! Since this wizard is experimental, please review the changes and make modifications as needed.
637
+ \nNext step: start internationalizing! See the docs for more information: https://generaltranslation.com/docs/react/tutorials/quickstart`);
638
+ logger.startCommand("Setting up project config...");
639
+ }
640
+ const reportManualAction = ({ whatHappened, fix }) => {
641
+ session.humanActions.push(fix);
642
+ logger.warn(createDiagnosticMessage({
643
+ source: "gt",
644
+ severity: "Warning",
645
+ whatHappened,
646
+ fix
647
+ }));
648
+ };
649
+ const reportSetupResult = ({ steps, manualActions }) => {
650
+ for (const step of steps) session.step(step);
651
+ manualActions.forEach(reportManualAction);
652
+ };
653
+ const translationFilesError = (error) => new Error(createDiagnosticMessage({
654
+ source: "gt",
655
+ severity: "Error",
656
+ whatHappened: `Could not create the translation files in ${translationsDir}`,
657
+ details: formatDiagnosticErrorDetails(error),
658
+ fix: "Choose another --translations-dir or fix the path, then rerun the command"
659
+ }));
660
+ const buildToolContext = {
661
+ appDirectory: cwd,
662
+ configFilepath,
663
+ defaultLocale: settings.defaultLocale,
664
+ locales: resolvedLocales,
665
+ translationsDir: storage === "local" ? translationsDir : void 0,
666
+ previousTranslationsDir: configuredTranslationsDir
667
+ };
668
+ if (storage === "local" && translationsDir) {
669
+ if (buildTool && !reactSetup) reportSetupResult(await buildTool.syncLoader({
670
+ ...buildToolContext,
671
+ translationsDir,
672
+ keepAppSource: setup.keepAppSource
673
+ }).catch((error) => {
674
+ throw translationFilesError(error);
675
+ }));
676
+ const generatedLoader = this.shouldGenerateLocalTranslationLoader(buildTool?.ownsLoader ?? false, runtimeSetup);
677
+ const loader = generatedLoader ? await createLoadTranslationsFile({
678
+ appDirectory: cwd,
679
+ translationsDir,
680
+ defaultLocale: settings.defaultLocale,
681
+ locales: resolvedLocales,
682
+ previousTranslationsDir: configuredTranslationsDir
683
+ }).catch((error) => {
684
+ throw translationFilesError(error);
685
+ }) : void 0;
686
+ if (loader === "created" || loader === "updated") session.step(`${loader} loadTranslations.js`);
687
+ if (loader === "custom" && translationsDir !== configuredTranslationsDir) {
688
+ const loaderFile = path.relative(cwd, findLoadTranslationsFiles(cwd)[0] ?? "loadTranslations.js");
689
+ reportManualAction({
690
+ whatHappened: `Your custom ${loaderFile} was left unchanged, but translations now go to ${translationsDir}`,
691
+ fix: `Update your custom ${loaderFile} to load translations from ${translationsDir}`
692
+ });
604
693
  }
605
- if (!areCredentialsSet(settings, envFramework)) {
606
- if (useDefaults || enableLiveTranslations ? true : await promptConfirm({
607
- message: "Would you like to set up a project ID and hot-reload key in .env.local?",
608
- defaultValue: true
609
- })) {
610
- try {
611
- await provisionDevelopmentCredentials(settings, envFramework);
612
- } catch (error) {
613
- logErrorAndExit(error instanceof UserAuthError ? error.message : createProjectCommandError("Failed to set up the development credentials", error));
614
- }
615
- logger.message(productionRuntimeKeyGuidance(settings.dashboardUrl));
616
- }
694
+ const guidance = this.getLocalTranslationGuidance({
695
+ generatedLoader: generatedLoader && loader !== "custom",
696
+ runtimeSetup,
697
+ translationsDir
698
+ });
699
+ if (guidance) logger.message(guidance);
700
+ } else if (storage === "cdn" && buildTool && !reactSetup && configuredTranslationsDir !== void 0) reportManualAction({
701
+ whatHappened: `Translations now load from the CDN, but ${buildTool.initializer} may still receive the local loader for ${configuredTranslationsDir}`,
702
+ fix: `Remove the loadTranslations option and its import from the ${buildTool.initializer}() call so translations load from the CDN`
703
+ });
704
+ else if (storage === "cdn" && !buildTool && configuredTranslationsDir !== void 0) {
705
+ const loaders = findLoadTranslationsFiles(cwd).map((loader) => path.relative(cwd, loader));
706
+ if (loaders.length > 0) {
707
+ const loaderFiles = loaders.join(", ");
708
+ const [them, loads] = loaders.length === 1 ? ["it", "loads"] : ["them", "load"];
709
+ reportManualAction({
710
+ whatHappened: `Translations now load from the CDN, but ${loaderFiles} still ${loads} them from ${configuredTranslationsDir}`,
711
+ fix: `Delete ${loaderFiles}, and remove ${them} from your GT setup if you pass ${them} there, so translations load from the CDN`
712
+ });
617
713
  }
618
714
  }
715
+ await createOrUpdateConfig(configFilepath, configUpdate);
716
+ session.step(`updated ${configFilepath}`);
717
+ logger.success(`Edit ${chalk.cyan(configFilepath)} to customize your translation setup. Docs: https://generaltranslation.com/docs/cli/reference/config`);
718
+ if (reactSetup && buildTool) reportSetupResult(await buildTool.apply(buildToolContext));
719
+ if (installGT && packageManager) await installWithProgress(session, "gt", packageManager, true);
720
+ if (project) {
721
+ await withCredentialsError(() => provisionDevelopmentCredentials(session, project, settings, envFramework, cwd));
722
+ logger.message(productionRuntimeKeyGuidance(settings.dashboardUrl));
723
+ }
619
724
  }
620
725
  };
621
726
  //#endregion