gt 2.15.0 → 2.16.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.
- package/CHANGELOG.md +30 -0
- package/dist/api/downloadFileBatch.js +28 -1
- package/dist/api/downloadFileBatch.js.map +1 -1
- package/dist/cli/base.js +16 -0
- package/dist/cli/base.js.map +1 -1
- package/dist/cli/commands/download.js +2 -1
- package/dist/cli/commands/download.js.map +1 -1
- package/dist/cli/commands/upload.js +5 -3
- package/dist/cli/commands/upload.js.map +1 -1
- package/dist/cli/flags.js +1 -1
- package/dist/cli/flags.js.map +1 -1
- package/dist/console/index.d.ts +3 -0
- package/dist/console/index.js +4 -1
- package/dist/console/index.js.map +1 -1
- package/dist/formats/files/aggregateFiles.js +32 -2
- package/dist/formats/files/aggregateFiles.js.map +1 -1
- package/dist/formats/files/collectFonts.d.ts +7 -0
- package/dist/formats/files/collectFonts.js +30 -0
- package/dist/formats/files/collectFonts.js.map +1 -0
- package/dist/formats/files/detectLottieExpressions.d.ts +8 -0
- package/dist/formats/files/detectLottieExpressions.js +42 -0
- package/dist/formats/files/detectLottieExpressions.js.map +1 -0
- package/dist/formats/files/supportedFiles.d.ts +2 -1
- package/dist/formats/files/supportedFiles.js +4 -2
- package/dist/formats/files/supportedFiles.js.map +1 -1
- package/dist/formats/files/transformFormat.d.ts +2 -0
- package/dist/formats/files/transformFormat.js +6 -3
- package/dist/formats/files/transformFormat.js.map +1 -1
- package/dist/fs/findFilepath.d.ts +7 -0
- package/dist/fs/findFilepath.js +11 -1
- package/dist/fs/findFilepath.js.map +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/workflows/enqueue.js +2 -0
- package/dist/workflows/enqueue.js.map +1 -1
- package/dist/workflows/stage.js +2 -0
- package/dist/workflows/stage.js.map +1 -1
- package/dist/workflows/upload.js +2 -0
- package/dist/workflows/upload.js.map +1 -1
- package/dist/workflows/utils/syncFonts.d.ts +9 -0
- package/dist/workflows/utils/syncFonts.js +25 -0
- package/dist/workflows/utils/syncFonts.js.map +1 -0
- package/package.json +5 -4
package/dist/cli/flags.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flags.js","names":[],"sources":["../../src/cli/flags.ts"],"sourcesContent":["import { Command } from 'commander';\nimport findFilepath from '../fs/findFilepath.js';\nimport { DEFAULT_GIT_REMOTE_NAME } from '../utils/constants.js';\n\nconst DEFAULT_TIMEOUT = 900;\n\nexport function attachSharedFlags(command: Command) {\n command\n .option(\n '-c, --config <path>',\n 'Filepath to config file, by default gt.config.json',\n findFilepath(['gt.config.json'])\n )\n .option('--api-key <key>', 'API key for General Translation cloud service')\n .option('--project-id <id>', 'General Translation project ID');\n return command;\n}\n\nexport function attachTranslateFlags(command: Command) {\n attachSharedFlags(command)\n .option('--version-id <id>', 'General Translation version ID')\n .option(\n '--default-language, --default-locale <locale>',\n 'Default locale (e.g., en)'\n )\n .option(\n '--new, --locales <locales...>',\n 'Space-separated list of locales (e.g., en fr es)'\n )\n .option(\n '--dry-run',\n 'Dry run, do not send updates to the General Translation API',\n false\n )\n .option(\n '--timeout <seconds>',\n 'Translation wait timeout in seconds',\n (value) => {\n const parsedValue = parseInt(value, 10);\n if (isNaN(parsedValue)) {\n throw new Error('Invalid timeout: not a number.');\n }\n if (parsedValue < 0) {\n throw new Error('Invalid timeout: must be a positive number.');\n }\n return parsedValue;\n },\n DEFAULT_TIMEOUT\n )\n .option(\n '--save-local',\n 'Detect and save local edits before enqueuing translations',\n
|
|
1
|
+
{"version":3,"file":"flags.js","names":[],"sources":["../../src/cli/flags.ts"],"sourcesContent":["import { Command } from 'commander';\nimport findFilepath from '../fs/findFilepath.js';\nimport { DEFAULT_GIT_REMOTE_NAME } from '../utils/constants.js';\n\nconst DEFAULT_TIMEOUT = 900;\n\nexport function attachSharedFlags(command: Command) {\n command\n .option(\n '-c, --config <path>',\n 'Filepath to config file, by default gt.config.json',\n findFilepath(['gt.config.json'])\n )\n .option('--api-key <key>', 'API key for General Translation cloud service')\n .option('--project-id <id>', 'General Translation project ID');\n return command;\n}\n\nexport function attachTranslateFlags(command: Command) {\n attachSharedFlags(command)\n .option('--version-id <id>', 'General Translation version ID')\n .option(\n '--default-language, --default-locale <locale>',\n 'Default locale (e.g., en)'\n )\n .option(\n '--new, --locales <locales...>',\n 'Space-separated list of locales (e.g., en fr es)'\n )\n .option(\n '--dry-run',\n 'Dry run, do not send updates to the General Translation API',\n false\n )\n .option(\n '--timeout <seconds>',\n 'Translation wait timeout in seconds',\n (value) => {\n const parsedValue = parseInt(value, 10);\n if (isNaN(parsedValue)) {\n throw new Error('Invalid timeout: not a number.');\n }\n if (parsedValue < 0) {\n throw new Error('Invalid timeout: must be a positive number.');\n }\n return parsedValue;\n },\n DEFAULT_TIMEOUT\n )\n .option(\n '--save-local',\n 'Detect and save local edits before enqueuing translations',\n true\n )\n .option(\n '--no-save-local',\n 'Skip detecting and saving local edits before enqueuing translations'\n )\n .option('--publish', 'Publish translations to the CDN', false)\n .option(\n '--experimental-localize-static-urls',\n '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.',\n false\n )\n .option(\n '--experimental-hide-default-locale',\n 'When localizing static locales, hide the default locale from the path',\n false\n )\n .option(\n '--experimental-flatten-json-files',\n 'Triggering this will flatten the json files into a single file. This is useful for projects that have a lot of json files.',\n false\n )\n .option(\n '--experimental-localize-static-imports',\n '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.',\n false\n )\n .option(\n '--experimental-localize-relative-assets',\n 'Triggering this will rewrite relative image asset URLs in translated md/mdx files to valid paths.',\n false\n )\n .option(\n '--force',\n 'Force a retranslation, invalidating all existing cached translations if they exist.',\n false\n )\n .option(\n '--force-download',\n 'Force download and overwrite local files, bypassing gt-lock.json checks.',\n false\n )\n .option(\n '--omit-config-ids',\n 'Do not write _versionId or _branchId to gt.config.json'\n )\n .option(\n '--experimental-clear-locale-dirs',\n 'Clear locale directories before downloading new translations',\n false\n )\n .option(\n '--branch <branch>',\n 'Specify a custom branch to use for translations'\n )\n .option(\n '--disable-branch-detection',\n 'Disable additional branch detection and optimizations and use the manually specified branch',\n false\n )\n .option('--enable-branching', 'Enable branching for the project')\n .option(\n '--remote-name <name>',\n 'Specify a custom remote name to use for branch detection',\n DEFAULT_GIT_REMOTE_NAME\n )\n .option(\n '--tag [value]',\n 'Tag this translation run (auto-resolves from git if no value provided)'\n )\n .option(\n '-m, --message <message>',\n 'Message to attach to the translation tag'\n );\n return command;\n}\n\n/**\n * Attaches flags necessary for parsing inline content\n * @param command - The command to attach the flags to\n * @returns The command with the inline content parsing flags attached\n */\nfunction attachInlineContentParsingFlags(command: Command) {\n return command\n .option(\n '--tsconfig, --jsconfig <path>',\n 'Path to custom jsconfig or tsconfig file',\n findFilepath(['./tsconfig.json', './jsconfig.json'])\n )\n .option('--dictionary <path>', 'Path to dictionary file')\n .option(\n '--src <paths...>',\n \"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}'\"\n )\n .option(\n '--inline',\n 'Include inline content in translations (e.g., inline jsx translations, inline string translations, etc.)',\n true\n );\n}\n\n/**\n * Attaches flags necessary for validating a project\n * @param command\n * @returns The command with the validate flags attached\n */\nexport function attachValidateFlags(command: Command) {\n return attachInlineContentParsingFlags(\n command.option(\n '-c, --config <path>',\n 'Filepath to config file, by default gt.config.json',\n findFilepath(['gt.config.json'])\n )\n );\n}\n\n/**\n * Attaches flags necessary for translating a project\n * @param command\n * @returns The command with the translate flags attached\n */\nexport function attachInlineTranslateFlags(command: Command) {\n return attachInlineContentParsingFlags(\n command.option(\n '--ignore-errors',\n 'Ignore errors encountered while scanning for inline content',\n false\n )\n );\n}\n"],"mappings":";;;AAIA,MAAM,kBAAkB;AAExB,SAAgB,kBAAkB,SAAkB;AAClD,SACG,OACC,uBACA,sDACA,aAAa,CAAC,iBAAiB,CAAC,CACjC,CACA,OAAO,mBAAmB,gDAAgD,CAC1E,OAAO,qBAAqB,iCAAiC;AAChE,QAAO;;AAGT,SAAgB,qBAAqB,SAAkB;AACrD,mBAAkB,QAAQ,CACvB,OAAO,qBAAqB,iCAAiC,CAC7D,OACC,iDACA,4BACD,CACA,OACC,iCACA,mDACD,CACA,OACC,aACA,+DACA,MACD,CACA,OACC,uBACA,wCACC,UAAU;EACT,MAAM,cAAc,SAAS,OAAO,GAAG;AACvC,MAAI,MAAM,YAAY,CACpB,OAAM,IAAI,MAAM,iCAAiC;AAEnD,MAAI,cAAc,EAChB,OAAM,IAAI,MAAM,8CAA8C;AAEhE,SAAO;IAET,gBACD,CACA,OACC,gBACA,6DACA,KACD,CACA,OACC,mBACA,sEACD,CACA,OAAO,aAAa,mCAAmC,MAAM,CAC7D,OACC,uCACA,iJACA,MACD,CACA,OACC,sCACA,yEACA,MACD,CACA,OACC,qCACA,8HACA,MACD,CACA,OACC,0CACA,2JACA,MACD,CACA,OACC,2CACA,qGACA,MACD,CACA,OACC,WACA,uFACA,MACD,CACA,OACC,oBACA,4EACA,MACD,CACA,OACC,qBACA,yDACD,CACA,OACC,oCACA,gEACA,MACD,CACA,OACC,qBACA,kDACD,CACA,OACC,8BACA,+FACA,MACD,CACA,OAAO,sBAAsB,mCAAmC,CAChE,OACC,wBACA,4DACA,wBACD,CACA,OACC,iBACA,yEACD,CACA,OACC,2BACA,2CACD;AACH,QAAO;;;;;;;AAQT,SAAS,gCAAgC,SAAkB;AACzD,QAAO,QACJ,OACC,iCACA,4CACA,aAAa,CAAC,mBAAmB,kBAAkB,CAAC,CACrD,CACA,OAAO,uBAAuB,0BAA0B,CACxD,OACC,oBACA,0MACD,CACA,OACC,YACA,4GACA,KACD;;;;;;;AAQL,SAAgB,oBAAoB,SAAkB;AACpD,QAAO,gCACL,QAAQ,OACN,uBACA,sDACA,aAAa,CAAC,iBAAiB,CAAC,CACjC,CACF;;;;;;;AAQH,SAAgB,2BAA2B,SAAkB;AAC3D,QAAO,gCACL,QAAQ,OACN,mBACA,+DACA,MACD,CACF"}
|
package/dist/console/index.d.ts
CHANGED
|
@@ -40,6 +40,9 @@ export declare const noSourceFileError = "No source translation file was found.
|
|
|
40
40
|
export declare const noApiKeyError = "No API key was found. Pass --api-key or set the GT_API_KEY environment variable.";
|
|
41
41
|
export declare const devApiKeyError = "Development API keys cannot be used with the General Translation API. Use a production API key instead.\nGenerate a production API key with: npx gt auth -t production";
|
|
42
42
|
export declare const noProjectIdError = "No project ID was found. Pass --project-id, add projectId to gt.config.json, or set the GT_PROJECT_ID environment variable.";
|
|
43
|
+
export declare const noVersionIdError = "No version ID was found. Pass --version-id or add _versionId to gt.config.json.";
|
|
43
44
|
export declare const invalidConfigurationError = "The files configuration cannot be used for this operation. Provide a valid download configuration or set --publish true to upload translations to the CDN.";
|
|
45
|
+
export declare const lottieTranslateError = "Lottie translations are processed asynchronously, so the translate command cannot complete them in one run. Use the stage and download flow instead:\n 1. Stage the files for translation: npx gt stage\n 2. Fetch the finished translations: npx gt download\nTranslations that are still processing are skipped, so re-run npx gt download until all of them have been downloaded.";
|
|
44
46
|
export declare const branchResolutionError = "The current git branch could not be resolved. Specify a branch explicitly or run the command from a git worktree with branch metadata available.";
|
|
47
|
+
export declare const lottieExpressionsError: (files: string[]) => string;
|
|
45
48
|
export declare const withOriginalError: (message: string, error: unknown) => string;
|
package/dist/console/index.js
CHANGED
|
@@ -53,10 +53,13 @@ const noSourceFileError = `No source translation file was found. Check your tran
|
|
|
53
53
|
const noApiKeyError = `No API key was found. Pass --api-key or set the GT_API_KEY environment variable.`;
|
|
54
54
|
const devApiKeyError = `Development API keys cannot be used with the General Translation API. Use a production API key instead.\nGenerate a production API key with: npx gt auth -t production`;
|
|
55
55
|
const noProjectIdError = `No project ID was found. Pass --project-id, add projectId to gt.config.json, or set the GT_PROJECT_ID environment variable.`;
|
|
56
|
+
const noVersionIdError = `No version ID was found. Pass --version-id or add _versionId to gt.config.json.`;
|
|
56
57
|
const invalidConfigurationError = `The files configuration cannot be used for this operation. Provide a valid download configuration or set --publish true to upload translations to the CDN.`;
|
|
58
|
+
const lottieTranslateError = `Lottie translations are processed asynchronously, so the translate command cannot complete them in one run. Use the stage and download flow instead:\n 1. Stage the files for translation: npx gt stage\n 2. Fetch the finished translations: npx gt download\nTranslations that are still processing are skipped, so re-run npx gt download until all of them have been downloaded.`;
|
|
57
59
|
const branchResolutionError = `The current git branch could not be resolved. Specify a branch explicitly or run the command from a git worktree with branch metadata available.`;
|
|
60
|
+
const lottieExpressionsError = (files) => `The following Lottie file(s) use After Effects expressions, which contain executable code and are not supported yet:\n${files.map((file) => ` - ${file}`).join("\n")}\nRe-export the animation with expressions baked into keyframes (or removed), then try again.`;
|
|
58
61
|
const withOriginalError = (message, error) => error != null ? `${message} Original error: ${String(error)}` : message;
|
|
59
62
|
//#endregion
|
|
60
|
-
export { branchResolutionError, devApiKeyError, invalidConfigurationError, noApiKeyError, noDefaultLocaleError, noFilesError, noLocalesError, noProjectIdError, noSourceFileError, warnApiKeyInConfigSync, warnAsyncUseGT, warnAutoderiveNoResultsSync, warnDataAttrOnBranch, warnDeprecatedTSugarPropsSync, warnDeriveCircularSpreadSync, warnDeriveDestructuringSync, warnDeriveFunctionNoResultsSync, warnDeriveFunctionNotWrappedSync, warnDeriveNonConstVariableSync, warnDeriveOptionalChainingSync, warnDeriveUnresolvableValueSync, warnDuplicateFunctionDefinitionSync, warnFailedToConstructJsxTreeSync, warnFunctionNotFoundSync, warnHasUnwrappedExpressionSync, warnInvalidDeclareVarNameSync, warnInvalidDeriveInitSync, warnInvalidFormatSync, warnInvalidIcuSync, warnInvalidMaxCharsSync, warnInvalidRequiresReviewSync, warnInvalidReturnSync, warnMissingReturnSync, warnNestedInternalTComponent, warnNestedTComponent, warnNonStaticExpressionSync, warnNonStringSync, warnRecursiveFunctionCallSync, warnSyncGetGT, warnTemplateLiteralSync, warnTernarySync, warnUnresolvedImportSync, warnVariablePropSync, withLocation, withOriginalError };
|
|
63
|
+
export { branchResolutionError, devApiKeyError, invalidConfigurationError, lottieExpressionsError, lottieTranslateError, noApiKeyError, noDefaultLocaleError, noFilesError, noLocalesError, noProjectIdError, noSourceFileError, noVersionIdError, warnApiKeyInConfigSync, warnAsyncUseGT, warnAutoderiveNoResultsSync, warnDataAttrOnBranch, warnDeprecatedTSugarPropsSync, warnDeriveCircularSpreadSync, warnDeriveDestructuringSync, warnDeriveFunctionNoResultsSync, warnDeriveFunctionNotWrappedSync, warnDeriveNonConstVariableSync, warnDeriveOptionalChainingSync, warnDeriveUnresolvableValueSync, warnDuplicateFunctionDefinitionSync, warnFailedToConstructJsxTreeSync, warnFunctionNotFoundSync, warnHasUnwrappedExpressionSync, warnInvalidDeclareVarNameSync, warnInvalidDeriveInitSync, warnInvalidFormatSync, warnInvalidIcuSync, warnInvalidMaxCharsSync, warnInvalidRequiresReviewSync, warnInvalidReturnSync, warnMissingReturnSync, warnNestedInternalTComponent, warnNestedTComponent, warnNonStaticExpressionSync, warnNonStringSync, warnRecursiveFunctionCallSync, warnSyncGetGT, warnTemplateLiteralSync, warnTernarySync, warnUnresolvedImportSync, warnVariablePropSync, withLocation, withOriginalError };
|
|
61
64
|
|
|
62
65
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/console/index.ts"],"sourcesContent":["import { createDiagnosticMessage } from 'generaltranslation/internal';\nimport { BRANCH_COMPONENT } from '../react/jsx/utils/constants.js';\nimport {\n colorizeFilepath,\n colorizeComponent,\n colorizeIdString,\n colorizeContent,\n colorizeLine,\n colorizeFunctionName,\n} from './colors.js';\nimport { formatCodeClamp } from './formatting.js';\n\nconst withWillErrorInNextVersion = (message: string): string =>\n `${message} (This will become an error in the next major version of the CLI.)`;\n\n// Derive function related errors\nconst withDeriveComponentError = (message: string): string =>\n `<Derive> rules violation: ${message}`;\n\nconst withDeriveFunctionError = (message: string): string =>\n `derive() rules violation: ${message}`;\n// Synchronous wrappers for backward compatibility\nexport const warnApiKeyInConfigSync = (optionsFilepath: string): string =>\n `${colorizeFilepath(\n optionsFilepath\n )}: Your API key is exposed! Remove it from the file and include it as an environment variable.`;\n\nexport const warnVariablePropSync = (\n file: string,\n attrName: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `${colorizeComponent('<T>')} component has dynamic attribute ${colorizeIdString(attrName)} with value: ${colorizeContent(\n value\n )}. Change ${colorizeIdString(attrName)} to ensure this content is translated.`,\n location\n );\n\nexport const warnInvalidReturnSync = (\n file: string,\n functionName: string,\n expression: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveComponentError(\n `Function ${colorizeFunctionName(functionName)} does not return a derivable (statically analyzable) expression. ${colorizeFunctionName(functionName)} must return either (1) a derivable string literal, (2) another derivable function invocation, (3) derivable JSX content, or (4) a ternary expression. Instead got:\\n${colorizeContent(expression)}`\n ),\n location\n );\n\n// TODO: this is temporary until we handle implicit returns\nexport const warnMissingReturnSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Function ${colorizeFunctionName(functionName)} is wrapped in ${colorizeComponent('<Derive>')} tags but does not have an explicit return statement. Derivable functions must have an explicit return statement.`,\n location\n );\n\nexport const warnHasUnwrappedExpressionSync = (\n file: string,\n unwrappedExpressions: string[],\n id?: string,\n location?: string\n): string =>\n withLocation(\n file,\n `${colorizeComponent('<T>')} component${\n id ? ` with id ${colorizeIdString(id)}` : ''\n } has children that could change at runtime. Use a variable component like ${colorizeComponent(\n '<Var>'\n )} to ensure this content is translated.\\n${colorizeContent(\n unwrappedExpressions.join('\\n')\n )}`,\n location\n );\n\nexport const warnFailedToConstructJsxTreeSync = (\n file: string,\n code: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Failed to construct JsxTree! Call expression is not a valid createElement call: ${colorizeContent(code)}`,\n location\n );\n\nexport const warnNestedTComponent = (file: string, location?: string): string =>\n withLocation(\n file,\n `Found nested <T> component. <T> components cannot be directly nested.`,\n location\n );\n\nexport const warnNestedInternalTComponent = (\n file: string,\n location?: string\n): string =>\n withLocation(\n file,\n `DEBUG: Found nested <GtInternalTranslateJsx> component. <GtInternalTranslateJsx> components cannot be directly nested.`,\n location\n );\n\nexport const warnNonStaticExpressionSync = (\n file: string,\n attrName: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found non-static expression for attribute ${colorizeIdString(\n attrName\n )}: ${colorizeContent(value)}. Change \"${colorizeIdString(attrName)}\" to ensure this content is translated.`,\n location\n );\n\nexport const warnInvalidMaxCharsSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found invalid maxChars value: ${colorizeContent(value)}. maxChars must be a numeric literal, e.g. maxChars={10}, to ensure this content is translated.`,\n location\n );\n\nexport const warnDeprecatedTSugarPropsSync = (locations: string[]): string =>\n createDiagnosticMessage({\n whatHappened: `Found ${locations.length} deprecated ${colorizeIdString(\n '$'\n )}-prefixed prop${locations.length === 1 ? '' : 's'} on ${colorizeComponent(\n '<T>'\n )} components`,\n fix: `Use the unprefixed forms instead (${colorizeIdString(\n 'context'\n )}, ${colorizeIdString('id')}, ${colorizeIdString('maxChars')}, ${colorizeIdString(\n 'requiresReview'\n )}); support for the ${colorizeIdString('$')}-prefixed forms will be removed in the next major version`,\n }) +\n '\\n' +\n locations.map((location) => ` ${colorizeLine(location)}`).join('\\n');\n\nexport const warnInvalidFormatSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found invalid $format value: ${colorizeContent(value)}. Must be one of: 'ICU', 'STRING', 'I18NEXT'.`,\n location\n );\n\nexport const warnInvalidRequiresReviewSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found invalid requiresReview value: ${colorizeContent(value)}. Must be a boolean literal (true or false) — strings and dynamic expressions are not allowed.`,\n location\n );\n\nexport const warnInvalidIcuSync = (\n file: string,\n value: string,\n error: string,\n location?: string\n): string =>\n withWillErrorInNextVersion(\n withLocation(\n file,\n `Found invalid ICU string: ${colorizeContent(value)}. Change the value to a valid ICU to ensure this content is translated. Error message: ${error}.`,\n location\n )\n );\n\nexport const warnTemplateLiteralSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found template literal with quasis (${colorizeContent(value)}). Change the template literal to a string to ensure this content is translated.`,\n location\n );\n\nexport const warnNonStringSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found non-string literal (${colorizeContent(value)}). Change the value to a string literal to ensure this content is translated.`,\n location\n );\n\nexport const warnAsyncUseGT = (file: string, location?: string): string =>\n withLocation(\n file,\n `Found useGT() in an async function. Use getGT() instead, or make the function synchronous.`,\n location\n );\n\nexport const warnSyncGetGT = (file: string, location?: string): string =>\n withLocation(\n file,\n `Found getGT() in a synchronous function. Use useGT() instead, or make the function async.`,\n location\n );\n\nexport const warnTernarySync = (file: string, location?: string): string =>\n withLocation(\n file,\n 'Found ternary expression. A Branch component may be more appropriate here.',\n location\n );\n\nexport const withLocation = (\n file: string,\n message: string,\n location?: string\n): string =>\n `${colorizeFilepath(file)}${location ? ` (${colorizeLine(location)})` : ''}: ${message}`;\n\nexport const warnFunctionNotFoundSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Function ${colorizeFunctionName(functionName)} definition could not be resolved. This might affect translation resolution for this ${colorizeComponent('<T>')} component.`,\n location\n );\n\nexport const warnInvalidDeclareVarNameSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found invalid declareVar() $name tag. Must be a derivable (statically analyzable) expression. Received: ${colorizeContent(value)}.`,\n location\n );\n\nexport const warnDuplicateFunctionDefinitionSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Function ${colorizeFunctionName(functionName)} is defined multiple times. Only the first definition will be used.`,\n location\n );\n\nexport const warnInvalidDeriveInitSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveComponentError(\n `The definition for ${colorizeFunctionName(functionName)} could not be resolved. When using arrow syntax to define a derivable (statically analyzable) function, the right hand side or the assignment MUST only contain the arrow function itself and no other expressions.\nExample: ${colorizeContent(`const ${colorizeFunctionName(functionName)} = () => { ... }`)}\nInvalid: ${colorizeContent(`const ${colorizeFunctionName(functionName)} = [() => { ... }][0]`)}`\n ),\n location\n );\n\nexport const warnDataAttrOnBranch = (\n file: string,\n attrName: string,\n location?: string\n): string =>\n withLocation(\n file,\n `${colorizeComponent(`<${BRANCH_COMPONENT}>`)} component ignores attributes prefixed with ${colorizeIdString('\"data-\"')}. Found ${colorizeIdString(attrName)}. Remove it or use a different attribute name.`,\n location\n );\n\nexport const warnRecursiveFunctionCallSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveComponentError(\n `Recursive function call detected: ${colorizeFunctionName(functionName)}. A derivable (statically analyzable) function cannot use recursive calls to construct its result.`\n ),\n location\n );\n\nexport const warnDeriveFunctionNotWrappedSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Could not resolve ${colorizeFunctionName(formatCodeClamp(functionName))}. This call is not wrapped in derive(). Ensure the function is properly wrapped with derive() and does not have circular import dependencies.`\n ),\n location\n );\n\nexport const warnDeriveNonConstVariableSync = (\n file: string,\n varName: string,\n kind: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Variable ${colorizeFunctionName(varName)} is declared with '${kind}' but only 'const' declarations can be resolved statically. Change it to 'const'.`\n ),\n location\n );\n\nexport const warnDeriveFunctionNoResultsSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Could not resolve ${colorizeFunctionName(formatCodeClamp(functionName))}. derive() can only receive function invocations and cannot use undefined values or looped calls to construct its result.`\n ),\n location\n );\n\nexport const warnAutoderiveNoResultsSync = (\n file: string,\n expression: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Autoderive could not resolve ${colorizeFunctionName(formatCodeClamp(expression))}. Only function calls with statically determinable return values can be used directly in t(). Consider wrapping with derive() for explicit derivation, or use an interpolation variable instead.`,\n location\n );\n\nexport const warnDeriveUnresolvableValueSync = (\n file: string,\n key: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Object property ${colorizeFunctionName(formatCodeClamp(key))} could not be resolved to a static string value. Only string literals, template literals, conditionals, and function calls returning strings are supported.`\n ),\n location\n );\n\nexport const warnDeriveCircularSpreadSync = (\n file: string,\n varName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Circular spread detected involving ${colorizeFunctionName(varName)}. Spread references that form a cycle cannot be resolved statically.`\n ),\n location\n );\n\nexport const warnDeriveDestructuringSync = (\n file: string,\n varName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Variable ${colorizeFunctionName(varName)} uses destructuring syntax, which is not yet supported in derive(). Assign the value to a const variable directly instead.`\n ),\n location\n );\n\nexport const warnDeriveOptionalChainingSync = (\n file: string,\n code: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Optional chaining (${colorizeFunctionName(formatCodeClamp(code))}) is not supported in derive(). Optional chaining implies the value could be undefined, which cannot be resolved statically. Use a non-optional access instead.`\n ),\n location\n );\n\nexport const warnUnresolvedImportSync = (\n file: string,\n functionName: string,\n importPath: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Could not resolve import ${colorizeFunctionName(importPath)} for function ${colorizeFunctionName(functionName)}. Translation strings inside this function will not be extracted.`,\n location\n );\n\n// Re-export error messages\nexport const noLocalesError = `No target locales were found. Add locales to gt.config.json or pass them with the --locales flag.`;\nexport const noDefaultLocaleError = `No default locale was found. Add defaultLocale to gt.config.json or pass it with --default-locale.`;\nexport const noFilesError = `The files configuration is missing or invalid. Check the files section in gt.config.json and try again.`;\nexport const noSourceFileError = `No source translation file was found. Check your translations directory and default locale configuration.`;\nexport const noApiKeyError = `No API key was found. Pass --api-key or set the GT_API_KEY environment variable.`;\nexport const devApiKeyError = `Development API keys cannot be used with the General Translation API. Use a production API key instead.\\nGenerate a production API key with: npx gt auth -t production`;\nexport const noProjectIdError = `No project ID was found. Pass --project-id, add projectId to gt.config.json, or set the GT_PROJECT_ID environment variable.`;\nexport const invalidConfigurationError = `The files configuration cannot be used for this operation. Provide a valid download configuration or set --publish true to upload translations to the CDN.`;\nexport const branchResolutionError = `The current git branch could not be resolved. Specify a branch explicitly or run the command from a git worktree with branch metadata available.`;\n\nexport const withOriginalError = (message: string, error: unknown): string =>\n error != null ? `${message} Original error: ${String(error)}` : message;\n"],"mappings":";;;;;AAYA,MAAM,8BAA8B,YAClC,GAAG,QAAQ;AAGb,MAAM,4BAA4B,YAChC,6BAA6B;AAE/B,MAAM,2BAA2B,YAC/B,6BAA6B;AAE/B,MAAa,0BAA0B,oBACrC,GAAG,iBACD,gBACD,CAAC;AAEJ,MAAa,wBACX,MACA,UACA,OACA,aAEA,aACE,MACA,GAAG,kBAAkB,MAAM,CAAC,mCAAmC,iBAAiB,SAAS,CAAC,eAAe,gBACvG,MACD,CAAC,WAAW,iBAAiB,SAAS,CAAC,yCACxC,SACD;AAEH,MAAa,yBACX,MACA,cACA,YACA,aAEA,aACE,MACA,yBACE,YAAY,qBAAqB,aAAa,CAAC,mEAAmE,qBAAqB,aAAa,CAAC,uKAAuK,gBAAgB,WAAW,GACxV,EACD,SACD;AAGH,MAAa,yBACX,MACA,cACA,aAEA,aACE,MACA,YAAY,qBAAqB,aAAa,CAAC,iBAAiB,kBAAkB,WAAW,CAAC,oHAC9F,SACD;AAEH,MAAa,kCACX,MACA,sBACA,IACA,aAEA,aACE,MACA,GAAG,kBAAkB,MAAM,CAAC,YAC1B,KAAK,YAAY,iBAAiB,GAAG,KAAK,GAC3C,4EAA4E,kBAC3E,QACD,CAAC,0CAA0C,gBAC1C,qBAAqB,KAAK,KAAK,CAChC,IACD,SACD;AAEH,MAAa,oCACX,MACA,MACA,aAEA,aACE,MACA,mFAAmF,gBAAgB,KAAK,IACxG,SACD;AAEH,MAAa,wBAAwB,MAAc,aACjD,aACE,MACA,yEACA,SACD;AAEH,MAAa,gCACX,MACA,aAEA,aACE,MACA,0HACA,SACD;AAEH,MAAa,+BACX,MACA,UACA,OACA,aAEA,aACE,MACA,6CAA6C,iBAC3C,SACD,CAAC,IAAI,gBAAgB,MAAM,CAAC,YAAY,iBAAiB,SAAS,CAAC,0CACpE,SACD;AAEH,MAAa,2BACX,MACA,OACA,aAEA,aACE,MACA,iCAAiC,gBAAgB,MAAM,CAAC,kGACxD,SACD;AAEH,MAAa,iCAAiC,cAC5C,wBAAwB;CACtB,cAAc,SAAS,UAAU,OAAO,cAAc,iBACpD,IACD,CAAC,gBAAgB,UAAU,WAAW,IAAI,KAAK,IAAI,MAAM,kBACxD,MACD,CAAC;CACF,KAAK,qCAAqC,iBACxC,UACD,CAAC,IAAI,iBAAiB,KAAK,CAAC,IAAI,iBAAiB,WAAW,CAAC,IAAI,iBAChE,iBACD,CAAC,qBAAqB,iBAAiB,IAAI,CAAC;CAC9C,CAAC,GACF,OACA,UAAU,KAAK,aAAa,KAAK,aAAa,SAAS,GAAG,CAAC,KAAK,KAAK;AAEvE,MAAa,yBACX,MACA,OACA,aAEA,aACE,MACA,gCAAgC,gBAAgB,MAAM,CAAC,gDACvD,SACD;AAEH,MAAa,iCACX,MACA,OACA,aAEA,aACE,MACA,uCAAuC,gBAAgB,MAAM,CAAC,iGAC9D,SACD;AAEH,MAAa,sBACX,MACA,OACA,OACA,aAEA,2BACE,aACE,MACA,6BAA6B,gBAAgB,MAAM,CAAC,yFAAyF,MAAM,IACnJ,SACD,CACF;AAEH,MAAa,2BACX,MACA,OACA,aAEA,aACE,MACA,uCAAuC,gBAAgB,MAAM,CAAC,mFAC9D,SACD;AAEH,MAAa,qBACX,MACA,OACA,aAEA,aACE,MACA,6BAA6B,gBAAgB,MAAM,CAAC,gFACpD,SACD;AAEH,MAAa,kBAAkB,MAAc,aAC3C,aACE,MACA,8FACA,SACD;AAEH,MAAa,iBAAiB,MAAc,aAC1C,aACE,MACA,6FACA,SACD;AAEH,MAAa,mBAAmB,MAAc,aAC5C,aACE,MACA,8EACA,SACD;AAEH,MAAa,gBACX,MACA,SACA,aAEA,GAAG,iBAAiB,KAAK,GAAG,WAAW,KAAK,aAAa,SAAS,CAAC,KAAK,GAAG,IAAI;AAEjF,MAAa,4BACX,MACA,cACA,aAEA,aACE,MACA,YAAY,qBAAqB,aAAa,CAAC,uFAAuF,kBAAkB,MAAM,CAAC,cAC/J,SACD;AAEH,MAAa,iCACX,MACA,OACA,aAEA,aACE,MACA,2GAA2G,gBAAgB,MAAM,CAAC,IAClI,SACD;AAEH,MAAa,uCACX,MACA,cACA,aAEA,aACE,MACA,YAAY,qBAAqB,aAAa,CAAC,sEAC/C,SACD;AAEH,MAAa,6BACX,MACA,cACA,aAEA,aACE,MACA,yBACE,sBAAsB,qBAAqB,aAAa,CAAC;WACpD,gBAAgB,SAAS,qBAAqB,aAAa,CAAC,kBAAkB,CAAC;WAC/E,gBAAgB,SAAS,qBAAqB,aAAa,CAAC,uBAAuB,GACzF,EACD,SACD;AAEH,MAAa,wBACX,MACA,UACA,aAEA,aACE,MACA,GAAG,kBAAkB,IAAI,iBAAiB,GAAG,CAAC,8CAA8C,iBAAiB,YAAU,CAAC,UAAU,iBAAiB,SAAS,CAAC,iDAC7J,SACD;AAEH,MAAa,iCACX,MACA,cACA,aAEA,aACE,MACA,yBACE,qCAAqC,qBAAqB,aAAa,CAAC,oGACzE,EACD,SACD;AAEH,MAAa,oCACX,MACA,cACA,aAEA,aACE,MACA,wBACE,qBAAqB,qBAAqB,gBAAgB,aAAa,CAAC,CAAC,+IAC1E,EACD,SACD;AAEH,MAAa,kCACX,MACA,SACA,MACA,aAEA,aACE,MACA,wBACE,YAAY,qBAAqB,QAAQ,CAAC,qBAAqB,KAAK,mFACrE,EACD,SACD;AAEH,MAAa,mCACX,MACA,cACA,aAEA,aACE,MACA,wBACE,qBAAqB,qBAAqB,gBAAgB,aAAa,CAAC,CAAC,2HAC1E,EACD,SACD;AAEH,MAAa,+BACX,MACA,YACA,aAEA,aACE,MACA,gCAAgC,qBAAqB,gBAAgB,WAAW,CAAC,CAAC,mMAClF,SACD;AAEH,MAAa,mCACX,MACA,KACA,aAEA,aACE,MACA,wBACE,mBAAmB,qBAAqB,gBAAgB,IAAI,CAAC,CAAC,6JAC/D,EACD,SACD;AAEH,MAAa,gCACX,MACA,SACA,aAEA,aACE,MACA,wBACE,sCAAsC,qBAAqB,QAAQ,CAAC,sEACrE,EACD,SACD;AAEH,MAAa,+BACX,MACA,SACA,aAEA,aACE,MACA,wBACE,YAAY,qBAAqB,QAAQ,CAAC,4HAC3C,EACD,SACD;AAEH,MAAa,kCACX,MACA,MACA,aAEA,aACE,MACA,wBACE,sBAAsB,qBAAqB,gBAAgB,KAAK,CAAC,CAAC,iKACnE,EACD,SACD;AAEH,MAAa,4BACX,MACA,cACA,YACA,aAEA,aACE,MACA,4BAA4B,qBAAqB,WAAW,CAAC,gBAAgB,qBAAqB,aAAa,CAAC,oEAChH,SACD;AAGH,MAAa,iBAAiB;AAC9B,MAAa,uBAAuB;AACpC,MAAa,eAAe;AAC5B,MAAa,oBAAoB;AACjC,MAAa,gBAAgB;AAC7B,MAAa,iBAAiB;AAC9B,MAAa,mBAAmB;AAChC,MAAa,4BAA4B;AACzC,MAAa,wBAAwB;AAErC,MAAa,qBAAqB,SAAiB,UACjD,SAAS,OAAO,GAAG,QAAQ,mBAAmB,OAAO,MAAM,KAAK"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/console/index.ts"],"sourcesContent":["import { createDiagnosticMessage } from 'generaltranslation/internal';\nimport { BRANCH_COMPONENT } from '../react/jsx/utils/constants.js';\nimport {\n colorizeFilepath,\n colorizeComponent,\n colorizeIdString,\n colorizeContent,\n colorizeLine,\n colorizeFunctionName,\n} from './colors.js';\nimport { formatCodeClamp } from './formatting.js';\n\nconst withWillErrorInNextVersion = (message: string): string =>\n `${message} (This will become an error in the next major version of the CLI.)`;\n\n// Derive function related errors\nconst withDeriveComponentError = (message: string): string =>\n `<Derive> rules violation: ${message}`;\n\nconst withDeriveFunctionError = (message: string): string =>\n `derive() rules violation: ${message}`;\n// Synchronous wrappers for backward compatibility\nexport const warnApiKeyInConfigSync = (optionsFilepath: string): string =>\n `${colorizeFilepath(\n optionsFilepath\n )}: Your API key is exposed! Remove it from the file and include it as an environment variable.`;\n\nexport const warnVariablePropSync = (\n file: string,\n attrName: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `${colorizeComponent('<T>')} component has dynamic attribute ${colorizeIdString(attrName)} with value: ${colorizeContent(\n value\n )}. Change ${colorizeIdString(attrName)} to ensure this content is translated.`,\n location\n );\n\nexport const warnInvalidReturnSync = (\n file: string,\n functionName: string,\n expression: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveComponentError(\n `Function ${colorizeFunctionName(functionName)} does not return a derivable (statically analyzable) expression. ${colorizeFunctionName(functionName)} must return either (1) a derivable string literal, (2) another derivable function invocation, (3) derivable JSX content, or (4) a ternary expression. Instead got:\\n${colorizeContent(expression)}`\n ),\n location\n );\n\n// TODO: this is temporary until we handle implicit returns\nexport const warnMissingReturnSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Function ${colorizeFunctionName(functionName)} is wrapped in ${colorizeComponent('<Derive>')} tags but does not have an explicit return statement. Derivable functions must have an explicit return statement.`,\n location\n );\n\nexport const warnHasUnwrappedExpressionSync = (\n file: string,\n unwrappedExpressions: string[],\n id?: string,\n location?: string\n): string =>\n withLocation(\n file,\n `${colorizeComponent('<T>')} component${\n id ? ` with id ${colorizeIdString(id)}` : ''\n } has children that could change at runtime. Use a variable component like ${colorizeComponent(\n '<Var>'\n )} to ensure this content is translated.\\n${colorizeContent(\n unwrappedExpressions.join('\\n')\n )}`,\n location\n );\n\nexport const warnFailedToConstructJsxTreeSync = (\n file: string,\n code: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Failed to construct JsxTree! Call expression is not a valid createElement call: ${colorizeContent(code)}`,\n location\n );\n\nexport const warnNestedTComponent = (file: string, location?: string): string =>\n withLocation(\n file,\n `Found nested <T> component. <T> components cannot be directly nested.`,\n location\n );\n\nexport const warnNestedInternalTComponent = (\n file: string,\n location?: string\n): string =>\n withLocation(\n file,\n `DEBUG: Found nested <GtInternalTranslateJsx> component. <GtInternalTranslateJsx> components cannot be directly nested.`,\n location\n );\n\nexport const warnNonStaticExpressionSync = (\n file: string,\n attrName: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found non-static expression for attribute ${colorizeIdString(\n attrName\n )}: ${colorizeContent(value)}. Change \"${colorizeIdString(attrName)}\" to ensure this content is translated.`,\n location\n );\n\nexport const warnInvalidMaxCharsSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found invalid maxChars value: ${colorizeContent(value)}. maxChars must be a numeric literal, e.g. maxChars={10}, to ensure this content is translated.`,\n location\n );\n\nexport const warnDeprecatedTSugarPropsSync = (locations: string[]): string =>\n createDiagnosticMessage({\n whatHappened: `Found ${locations.length} deprecated ${colorizeIdString(\n '$'\n )}-prefixed prop${locations.length === 1 ? '' : 's'} on ${colorizeComponent(\n '<T>'\n )} components`,\n fix: `Use the unprefixed forms instead (${colorizeIdString(\n 'context'\n )}, ${colorizeIdString('id')}, ${colorizeIdString('maxChars')}, ${colorizeIdString(\n 'requiresReview'\n )}); support for the ${colorizeIdString('$')}-prefixed forms will be removed in the next major version`,\n }) +\n '\\n' +\n locations.map((location) => ` ${colorizeLine(location)}`).join('\\n');\n\nexport const warnInvalidFormatSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found invalid $format value: ${colorizeContent(value)}. Must be one of: 'ICU', 'STRING', 'I18NEXT'.`,\n location\n );\n\nexport const warnInvalidRequiresReviewSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found invalid requiresReview value: ${colorizeContent(value)}. Must be a boolean literal (true or false) — strings and dynamic expressions are not allowed.`,\n location\n );\n\nexport const warnInvalidIcuSync = (\n file: string,\n value: string,\n error: string,\n location?: string\n): string =>\n withWillErrorInNextVersion(\n withLocation(\n file,\n `Found invalid ICU string: ${colorizeContent(value)}. Change the value to a valid ICU to ensure this content is translated. Error message: ${error}.`,\n location\n )\n );\n\nexport const warnTemplateLiteralSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found template literal with quasis (${colorizeContent(value)}). Change the template literal to a string to ensure this content is translated.`,\n location\n );\n\nexport const warnNonStringSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found non-string literal (${colorizeContent(value)}). Change the value to a string literal to ensure this content is translated.`,\n location\n );\n\nexport const warnAsyncUseGT = (file: string, location?: string): string =>\n withLocation(\n file,\n `Found useGT() in an async function. Use getGT() instead, or make the function synchronous.`,\n location\n );\n\nexport const warnSyncGetGT = (file: string, location?: string): string =>\n withLocation(\n file,\n `Found getGT() in a synchronous function. Use useGT() instead, or make the function async.`,\n location\n );\n\nexport const warnTernarySync = (file: string, location?: string): string =>\n withLocation(\n file,\n 'Found ternary expression. A Branch component may be more appropriate here.',\n location\n );\n\nexport const withLocation = (\n file: string,\n message: string,\n location?: string\n): string =>\n `${colorizeFilepath(file)}${location ? ` (${colorizeLine(location)})` : ''}: ${message}`;\n\nexport const warnFunctionNotFoundSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Function ${colorizeFunctionName(functionName)} definition could not be resolved. This might affect translation resolution for this ${colorizeComponent('<T>')} component.`,\n location\n );\n\nexport const warnInvalidDeclareVarNameSync = (\n file: string,\n value: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Found invalid declareVar() $name tag. Must be a derivable (statically analyzable) expression. Received: ${colorizeContent(value)}.`,\n location\n );\n\nexport const warnDuplicateFunctionDefinitionSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Function ${colorizeFunctionName(functionName)} is defined multiple times. Only the first definition will be used.`,\n location\n );\n\nexport const warnInvalidDeriveInitSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveComponentError(\n `The definition for ${colorizeFunctionName(functionName)} could not be resolved. When using arrow syntax to define a derivable (statically analyzable) function, the right hand side or the assignment MUST only contain the arrow function itself and no other expressions.\nExample: ${colorizeContent(`const ${colorizeFunctionName(functionName)} = () => { ... }`)}\nInvalid: ${colorizeContent(`const ${colorizeFunctionName(functionName)} = [() => { ... }][0]`)}`\n ),\n location\n );\n\nexport const warnDataAttrOnBranch = (\n file: string,\n attrName: string,\n location?: string\n): string =>\n withLocation(\n file,\n `${colorizeComponent(`<${BRANCH_COMPONENT}>`)} component ignores attributes prefixed with ${colorizeIdString('\"data-\"')}. Found ${colorizeIdString(attrName)}. Remove it or use a different attribute name.`,\n location\n );\n\nexport const warnRecursiveFunctionCallSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveComponentError(\n `Recursive function call detected: ${colorizeFunctionName(functionName)}. A derivable (statically analyzable) function cannot use recursive calls to construct its result.`\n ),\n location\n );\n\nexport const warnDeriveFunctionNotWrappedSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Could not resolve ${colorizeFunctionName(formatCodeClamp(functionName))}. This call is not wrapped in derive(). Ensure the function is properly wrapped with derive() and does not have circular import dependencies.`\n ),\n location\n );\n\nexport const warnDeriveNonConstVariableSync = (\n file: string,\n varName: string,\n kind: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Variable ${colorizeFunctionName(varName)} is declared with '${kind}' but only 'const' declarations can be resolved statically. Change it to 'const'.`\n ),\n location\n );\n\nexport const warnDeriveFunctionNoResultsSync = (\n file: string,\n functionName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Could not resolve ${colorizeFunctionName(formatCodeClamp(functionName))}. derive() can only receive function invocations and cannot use undefined values or looped calls to construct its result.`\n ),\n location\n );\n\nexport const warnAutoderiveNoResultsSync = (\n file: string,\n expression: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Autoderive could not resolve ${colorizeFunctionName(formatCodeClamp(expression))}. Only function calls with statically determinable return values can be used directly in t(). Consider wrapping with derive() for explicit derivation, or use an interpolation variable instead.`,\n location\n );\n\nexport const warnDeriveUnresolvableValueSync = (\n file: string,\n key: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Object property ${colorizeFunctionName(formatCodeClamp(key))} could not be resolved to a static string value. Only string literals, template literals, conditionals, and function calls returning strings are supported.`\n ),\n location\n );\n\nexport const warnDeriveCircularSpreadSync = (\n file: string,\n varName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Circular spread detected involving ${colorizeFunctionName(varName)}. Spread references that form a cycle cannot be resolved statically.`\n ),\n location\n );\n\nexport const warnDeriveDestructuringSync = (\n file: string,\n varName: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Variable ${colorizeFunctionName(varName)} uses destructuring syntax, which is not yet supported in derive(). Assign the value to a const variable directly instead.`\n ),\n location\n );\n\nexport const warnDeriveOptionalChainingSync = (\n file: string,\n code: string,\n location?: string\n): string =>\n withLocation(\n file,\n withDeriveFunctionError(\n `Optional chaining (${colorizeFunctionName(formatCodeClamp(code))}) is not supported in derive(). Optional chaining implies the value could be undefined, which cannot be resolved statically. Use a non-optional access instead.`\n ),\n location\n );\n\nexport const warnUnresolvedImportSync = (\n file: string,\n functionName: string,\n importPath: string,\n location?: string\n): string =>\n withLocation(\n file,\n `Could not resolve import ${colorizeFunctionName(importPath)} for function ${colorizeFunctionName(functionName)}. Translation strings inside this function will not be extracted.`,\n location\n );\n\n// Re-export error messages\nexport const noLocalesError = `No target locales were found. Add locales to gt.config.json or pass them with the --locales flag.`;\nexport const noDefaultLocaleError = `No default locale was found. Add defaultLocale to gt.config.json or pass it with --default-locale.`;\nexport const noFilesError = `The files configuration is missing or invalid. Check the files section in gt.config.json and try again.`;\nexport const noSourceFileError = `No source translation file was found. Check your translations directory and default locale configuration.`;\nexport const noApiKeyError = `No API key was found. Pass --api-key or set the GT_API_KEY environment variable.`;\nexport const devApiKeyError = `Development API keys cannot be used with the General Translation API. Use a production API key instead.\\nGenerate a production API key with: npx gt auth -t production`;\nexport const noProjectIdError = `No project ID was found. Pass --project-id, add projectId to gt.config.json, or set the GT_PROJECT_ID environment variable.`;\nexport const noVersionIdError = `No version ID was found. Pass --version-id or add _versionId to gt.config.json.`;\nexport const invalidConfigurationError = `The files configuration cannot be used for this operation. Provide a valid download configuration or set --publish true to upload translations to the CDN.`;\nexport const lottieTranslateError = `Lottie translations are processed asynchronously, so the translate command cannot complete them in one run. Use the stage and download flow instead:\\n 1. Stage the files for translation: npx gt stage\\n 2. Fetch the finished translations: npx gt download\\nTranslations that are still processing are skipped, so re-run npx gt download until all of them have been downloaded.`;\nexport const branchResolutionError = `The current git branch could not be resolved. Specify a branch explicitly or run the command from a git worktree with branch metadata available.`;\n\nexport const lottieExpressionsError = (files: string[]): string =>\n `The following Lottie file(s) use After Effects expressions, which contain executable code and are not supported yet:\\n${files\n .map((file) => ` - ${file}`)\n .join(\n '\\n'\n )}\\nRe-export the animation with expressions baked into keyframes (or removed), then try again.`;\n\nexport const withOriginalError = (message: string, error: unknown): string =>\n error != null ? `${message} Original error: ${String(error)}` : message;\n"],"mappings":"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"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { logger } from "../../console/logger.js";
|
|
2
2
|
import { hashStringSync, hashVersionId } from "../../utils/hash.js";
|
|
3
|
-
import {
|
|
3
|
+
import { logErrorAndExit } from "../../console/logging.js";
|
|
4
|
+
import { getRelative, readBinaryFileBase64, readFile } from "../../fs/findFilepath.js";
|
|
5
|
+
import { lottieExpressionsError } from "../../console/index.js";
|
|
4
6
|
import { SUPPORTED_FILE_EXTENSIONS } from "./supportedFiles.js";
|
|
5
7
|
import { getTransformFormatProperty } from "./transformFormat.js";
|
|
6
8
|
import { determineLibrary } from "../../fs/determineFramework/index.js";
|
|
7
9
|
import { flattenJson } from "../json/flattenJson.js";
|
|
8
10
|
import { recordWarning } from "../../state/translateWarnings.js";
|
|
9
11
|
import { buildPublishMap } from "../../utils/resolvePublish.js";
|
|
12
|
+
import { lottieHasExpressions } from "./detectLottieExpressions.js";
|
|
10
13
|
import { parseJson } from "../json/parseJson.js";
|
|
11
14
|
import { resolveMintlifyRefs, shouldResolveRefs } from "../../utils/resolveMintlifyRefs.js";
|
|
12
15
|
import { storeRefMap } from "../../state/mintlifyRefMap.js";
|
|
@@ -177,8 +180,35 @@ async function aggregateFiles(settings) {
|
|
|
177
180
|
});
|
|
178
181
|
files.push(...twilioContentJsonFiles.filter((file) => file !== null));
|
|
179
182
|
}
|
|
183
|
+
if (filePaths.lottie) {
|
|
184
|
+
const expressionOffenders = [];
|
|
185
|
+
const lottieFiles = filePaths.lottie.map((filePath) => {
|
|
186
|
+
const content = readBinaryFileBase64(filePath);
|
|
187
|
+
const relativePath = getRelative(filePath);
|
|
188
|
+
if (!content) {
|
|
189
|
+
logger.warn(`Skipping ${relativePath}: file is empty or unreadable`);
|
|
190
|
+
recordWarning("skipped_file", relativePath, "File is empty or unreadable");
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
if (lottieHasExpressions(content)) {
|
|
194
|
+
expressionOffenders.push(relativePath);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
content,
|
|
199
|
+
fileName: relativePath,
|
|
200
|
+
fileFormat: "LOTTIE",
|
|
201
|
+
...getTransformFormatProperty(settings, "lottie"),
|
|
202
|
+
fileId: hashStringSync(relativePath),
|
|
203
|
+
versionId: hashStringSync(content),
|
|
204
|
+
locale: settings.defaultLocale
|
|
205
|
+
};
|
|
206
|
+
}).filter((file) => file !== null);
|
|
207
|
+
if (expressionOffenders.length > 0) logErrorAndExit(lottieExpressionsError(expressionOffenders));
|
|
208
|
+
files.push(...lottieFiles);
|
|
209
|
+
}
|
|
180
210
|
for (const fileType of SUPPORTED_FILE_EXTENSIONS) {
|
|
181
|
-
if (fileType === "json" || fileType === "yaml" || fileType === "twilioContentJson") continue;
|
|
211
|
+
if (fileType === "json" || fileType === "yaml" || fileType === "twilioContentJson" || fileType === "lottie") continue;
|
|
182
212
|
if (filePaths[fileType]) {
|
|
183
213
|
const parsed = filePaths[fileType].map((filePath) => {
|
|
184
214
|
const content = readFile(filePath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregateFiles.js","names":[],"sources":["../../../src/formats/files/aggregateFiles.ts"],"sourcesContent":["import { logger } from '../../console/logger.js';\nimport { recordWarning } from '../../state/translateWarnings.js';\nimport { getRelative, readFile } from '../../fs/findFilepath.js';\nimport { Settings } from '../../types/index.js';\nimport type { FileFormat, DataFormat, FileToUpload } from '../../types/data.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';\nimport { parseJson } from '../json/parseJson.js';\nimport {\n resolveMintlifyRefs,\n shouldResolveRefs,\n} from '../../utils/resolveMintlifyRefs.js';\nimport { storeRefMap } from '../../state/mintlifyRefMap.js';\nimport parseYaml from '../yaml/parseYaml.js';\nimport { validateYamlSchema } from '../yaml/utils.js';\nimport { flattenJson } from '../json/flattenJson.js';\nimport type { JSONObject } from '../../types/data/json.js';\nimport YAML from 'yaml';\nimport { determineLibrary } from '../../fs/determineFramework/index.js';\nimport { hashStringSync, hashVersionId } from '../../utils/hash.js';\nimport { preprocessContent } from './preprocessContent.js';\nimport {\n parseKeyedMetadata,\n type KeyedMetadata,\n} from '../parseKeyedMetadata.js';\nimport { buildPublishMap } from '../../utils/resolvePublish.js';\nimport { getTransformFormatProperty } from './transformFormat.js';\n\n/**\n * Checks if a file path is a metadata companion file (e.g. foo.metadata.json)\n * AND its corresponding source file (e.g. foo.json) exists in the file list.\n * If both conditions are true, the metadata file should be skipped as a translation source.\n */\nfunction isCompanionMetadataFile(\n filePath: string,\n allFilePaths: string[]\n): boolean {\n const metadataPattern = /\\.metadata\\.(json|yaml|yml)$/;\n if (!metadataPattern.test(filePath)) return false;\n\n // Derive the source file path: foo.metadata.json -> foo.json\n const sourceFilePath = filePath.replace(\n /\\.metadata\\.(json|yaml|yml)$/,\n '.$1'\n );\n return allFilePaths.includes(sourceFilePath);\n}\n\nexport async function aggregateFiles(\n settings: Settings\n): Promise<{ files: FileToUpload[]; publishMap: Map<string, boolean> }> {\n // Aggregate all files to translate\n const files: FileToUpload[] = [];\n if (\n !settings.files ||\n (Object.keys(settings.files.placeholderPaths).length === 1 &&\n settings.files.placeholderPaths.gt)\n ) {\n return { files, publishMap: new Map<string, boolean>() };\n }\n\n const { resolvedPaths: filePaths } = settings.files;\n // Tolerate partially-constructed settings from programmatic callers\n const requiresReviewPaths =\n settings.files.requiresReviewPaths ?? new Set<string>();\n const skipValidation = settings.options?.skipFileValidation;\n\n // Build publish map upfront from resolved paths.\n const publishMap = buildPublishMap(filePaths, settings);\n\n // Process JSON files\n if (filePaths.json) {\n const { library, additionalModules } = determineLibrary();\n\n // Determine dataFormat for JSONs\n let dataFormat: DataFormat;\n if (library === 'next-intl') {\n dataFormat = 'ICU';\n } else if (library === 'i18next') {\n if (additionalModules.includes('i18next-icu')) {\n dataFormat = 'ICU';\n } else {\n dataFormat = 'I18NEXT';\n }\n } else {\n dataFormat = 'STRING';\n }\n\n const jsonFiles = filePaths.json\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.json!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n // Resolve $ref before parsing if configured\n let contentForParsing = content;\n if (shouldResolveRefs(filePath, settings.options)) {\n try {\n const json = JSON.parse(content);\n const { resolved, refMap } = resolveMintlifyRefs(json, filePath);\n storeRefMap(refMap);\n contentForParsing = JSON.stringify(resolved, null, 2);\n } catch {\n // JSON parse errors are handled below by parseJson\n }\n }\n\n const parsedJson = parseJson(\n contentForParsing,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n let parsedContent: JSONObject | undefined;\n try {\n parsedContent = JSON.parse(content) as JSONObject;\n } catch {\n // Content not parsable — skip metadata detection\n }\n if (parsedContent) {\n const rawMetadata = parseKeyedMetadata(filePath, parsedContent);\n if (rawMetadata) {\n // Run metadata through the same include/composite schema as the source\n // so key paths align at translation time\n const transformed = parseJson(\n JSON.stringify(rawMetadata),\n filePath,\n settings.options || {},\n settings.defaultLocale,\n false\n );\n const transformedMetadata = JSON.parse(transformed);\n\n // Filter metadata to only keep keys that exist in the transformed source\n // This prevents misaligned entries from wide JSONPath patterns\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedJson)));\n const filtered = Object.fromEntries(\n Object.entries(transformedMetadata).filter(([k]) =>\n sourceKeys.has(k)\n )\n ) as KeyedMetadata;\n\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the JSON schema — metadata was not attached`\n );\n }\n }\n }\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedJson,\n requiresReviewPaths.has(filePath)\n ),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'JSON' as const,\n ...getTransformFormatProperty(settings, 'json'),\n dataFormat,\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...jsonFiles.filter((file) => file !== null));\n }\n\n // Process YAML files\n if (filePaths.yaml) {\n const yamlFiles = filePaths.yaml\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.yaml!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate YAML parseability\n if (!skipValidation?.yaml) {\n try {\n YAML.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: YAML file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'YAML file is not parsable'\n );\n return null;\n }\n }\n\n const { content: parsedYaml, fileFormat } = parseYaml(\n content,\n filePath,\n settings.options || {}\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n try {\n const parsedYamlContent = YAML.parse(content);\n const rawMetadata = parseKeyedMetadata(filePath, parsedYamlContent);\n if (rawMetadata) {\n const yamlSchema = validateYamlSchema(\n settings.options || {},\n filePath\n );\n if (yamlSchema?.include) {\n // Flatten metadata through the same include schema as the source\n const flattened = flattenJson(rawMetadata, yamlSchema.include);\n // Filter to only keep keys that exist in the transformed source\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedYaml)));\n const filtered = Object.fromEntries(\n Object.entries(flattened).filter(([k]) => sourceKeys.has(k))\n ) as KeyedMetadata;\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the YAML schema — metadata was not attached`\n );\n }\n } else {\n keyedMetadata = rawMetadata;\n }\n }\n } catch {\n // Content not parsable as YAML — skip metadata detection\n }\n\n return {\n content: parsedYaml,\n fileName: relativePath,\n fileFormat,\n ...getTransformFormatProperty(settings, 'yaml'),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedYaml,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file || typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: YAML file is empty`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'YAML file is empty'\n );\n return false;\n }\n return true;\n });\n files.push(...yamlFiles.filter((file) => file !== null));\n }\n\n // Process Twilio Content JSON files\n if (filePaths.twilioContentJson) {\n const twilioContentJsonFiles = filePaths.twilioContentJson\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n const parsedJson = parseJson(\n content,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedJson,\n requiresReviewPaths.has(filePath)\n ),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'TWILIO_CONTENT_JSON' as const,\n ...getTransformFormatProperty(settings, 'twilioContentJson'),\n dataFormat: 'STRING' as const,\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...twilioContentJsonFiles.filter((file) => file !== null));\n }\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (\n fileType === 'json' ||\n fileType === 'yaml' ||\n fileType === 'twilioContentJson'\n )\n continue;\n if (filePaths[fileType]) {\n const parsed = filePaths[fileType]\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n const processed = preprocessContent(\n content,\n relativePath,\n fileType,\n settings\n );\n\n if (typeof processed !== 'string') {\n logger.warn(`Skipping ${relativePath}: ${processed.skip}`);\n recordWarning('skipped_file', relativePath, processed.skip);\n return null;\n }\n\n return {\n content: processed,\n fileName: relativePath,\n fileFormat: fileType.toUpperCase() as FileFormat,\n ...getTransformFormatProperty(settings, fileType),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n processed,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (\n !file ||\n typeof file.content !== 'string' ||\n !file.content.trim()\n ) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: File is empty after sanitization`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'File is empty after sanitization'\n );\n return false;\n }\n return true;\n });\n files.push(...parsed.filter((file) => file !== null));\n }\n }\n\n // Remove stale entries for files that were skipped during validation\n const validFileIds = new Set(files.map((f) => f.fileId));\n for (const fileId of publishMap.keys()) {\n if (!validFileIds.has(fileId)) {\n publishMap.delete(fileId);\n }\n }\n\n return { files, publishMap };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAS,wBACP,UACA,cACS;AAET,KAAI,CAAC,+BAAgB,KAAK,SAAS,CAAE,QAAO;CAG5C,MAAM,iBAAiB,SAAS,QAC9B,gCACA,MACD;AACD,QAAO,aAAa,SAAS,eAAe;;AAG9C,eAAsB,eACpB,UACsE;CAEtE,MAAM,QAAwB,EAAE;AAChC,KACE,CAAC,SAAS,SACT,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC,WAAW,KACvD,SAAS,MAAM,iBAAiB,GAElC,QAAO;EAAE;EAAO,4BAAY,IAAI,KAAsB;EAAE;CAG1D,MAAM,EAAE,eAAe,cAAc,SAAS;CAE9C,MAAM,sBACJ,SAAS,MAAM,uCAAuB,IAAI,KAAa;CACzD,MAAM,iBAAiB,SAAS,SAAS;CAGzC,MAAM,aAAa,gBAAgB,WAAW,SAAS;AAGvD,KAAI,UAAU,MAAM;EAClB,MAAM,EAAE,SAAS,sBAAsB,kBAAkB;EAGzD,IAAI;AACJ,MAAI,YAAY,YACd,cAAa;WACJ,YAAY,UACrB,KAAI,kBAAkB,SAAS,cAAc,CAC3C,cAAa;MAEb,cAAa;MAGf,cAAa;EAGf,MAAM,YAAY,UAAU,KACzB,QAAQ,aAAa,CAAC,wBAAwB,UAAU,UAAU,KAAM,CAAC,CACzE,KAAK,aAAa;GACjB,MAAM,UAAU,SAAS,SAAS;GAClC,MAAM,eAAe,YAAY,SAAS;AAG1C,OAAI,CAAC,gBAAgB,KACnB,KAAI;AACF,SAAK,MAAM,QAAQ;WACb;AACN,WAAO,KAAK,YAAY,aAAa,6BAA6B;AAClE,kBACE,gBACA,cACA,4BACD;AACD,WAAO;;GAKX,IAAI,oBAAoB;AACxB,OAAI,kBAAkB,UAAU,SAAS,QAAQ,CAC/C,KAAI;IAEF,MAAM,EAAE,UAAU,WAAW,oBADhB,KAAK,MAAM,QAC6B,EAAE,SAAS;AAChE,gBAAY,OAAO;AACnB,wBAAoB,KAAK,UAAU,UAAU,MAAM,EAAE;WAC/C;GAKV,MAAM,aAAa,UACjB,mBACA,UACA,SAAS,WAAW,EAAE,EACtB,SAAS,cACV;GAGD,IAAI;GACJ,IAAI;AACJ,OAAI;AACF,oBAAgB,KAAK,MAAM,QAAQ;WAC7B;AAGR,OAAI,eAAe;IACjB,MAAM,cAAc,mBAAmB,UAAU,cAAc;AAC/D,QAAI,aAAa;KAGf,MAAM,cAAc,UAClB,KAAK,UAAU,YAAY,EAC3B,UACA,SAAS,WAAW,EAAE,EACtB,SAAS,eACT,MACD;KACD,MAAM,sBAAsB,KAAK,MAAM,YAAY;KAInD,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,KAAK,MAAM,WAAW,CAAC,CAAC;KAC/D,MAAM,WAAW,OAAO,YACtB,OAAO,QAAQ,oBAAoB,CAAC,QAAQ,CAAC,OAC3C,WAAW,IAAI,EAAE,CAClB,CACF;AAED,SAAI,OAAO,KAAK,SAAS,CAAC,SAAS,EACjC,iBAAgB;SAEhB,QAAO,KACL,gCAAgC,aAAa,uEAC9C;;;AAKP,UAAO;IACL,QAAQ,eAAe,aAAa;IACpC,WAAW,cACT,YACA,oBAAoB,IAAI,SAAS,CAClC;IACD,SAAS;IACT,UAAU;IACV,YAAY;IACZ,GAAG,2BAA2B,UAAU,OAAO;IAC/C;IACA,QAAQ,SAAS;IACjB,GAAI,iBAAiB,EACnB,gBAAgB,EAAE,eAAe,EAClC;IACF;IACD,CACD,QAAQ,SAAS;AAChB,OAAI,CAAC,KAAM,QAAO;AAClB,OAAI,OAAO,KAAK,YAAY,YAAY,CAAC,KAAK,QAAQ,MAAM,EAAE;AAC5D,WAAO,KAAK,YAAY,KAAK,SAAS,sBAAsB;AAC5D,kBAAc,gBAAgB,KAAK,UAAU,qBAAqB;AAClE,WAAO;;AAET,UAAO;IACP;AACJ,QAAM,KAAK,GAAG,UAAU,QAAQ,SAAS,SAAS,KAAK,CAAC;;AAI1D,KAAI,UAAU,MAAM;EAClB,MAAM,YAAY,UAAU,KACzB,QAAQ,aAAa,CAAC,wBAAwB,UAAU,UAAU,KAAM,CAAC,CACzE,KAAK,aAAa;GACjB,MAAM,UAAU,SAAS,SAAS;GAClC,MAAM,eAAe,YAAY,SAAS;AAG1C,OAAI,CAAC,gBAAgB,KACnB,KAAI;AACF,SAAK,MAAM,QAAQ;WACb;AACN,WAAO,KAAK,YAAY,aAAa,6BAA6B;AAClE,kBACE,gBACA,cACA,4BACD;AACD,WAAO;;GAIX,MAAM,EAAE,SAAS,YAAY,eAAe,UAC1C,SACA,UACA,SAAS,WAAW,EAAE,CACvB;GAGD,IAAI;AACJ,OAAI;IAEF,MAAM,cAAc,mBAAmB,UADb,KAAK,MAAM,QAC6B,CAAC;AACnE,QAAI,aAAa;KACf,MAAM,aAAa,mBACjB,SAAS,WAAW,EAAE,EACtB,SACD;AACD,SAAI,YAAY,SAAS;MAEvB,MAAM,YAAY,YAAY,aAAa,WAAW,QAAQ;MAE9D,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,KAAK,MAAM,WAAW,CAAC,CAAC;MAC/D,MAAM,WAAW,OAAO,YACtB,OAAO,QAAQ,UAAU,CAAC,QAAQ,CAAC,OAAO,WAAW,IAAI,EAAE,CAAC,CAC7D;AACD,UAAI,OAAO,KAAK,SAAS,CAAC,SAAS,EACjC,iBAAgB;UAEhB,QAAO,KACL,gCAAgC,aAAa,uEAC9C;WAGH,iBAAgB;;WAGd;AAIR,UAAO;IACL,SAAS;IACT,UAAU;IACV;IACA,GAAG,2BAA2B,UAAU,OAAO;IAC/C,QAAQ,eAAe,aAAa;IACpC,WAAW,cACT,YACA,oBAAoB,IAAI,SAAS,CAClC;IACD,QAAQ,SAAS;IACjB,GAAI,iBAAiB,EACnB,gBAAgB,EAAE,eAAe,EAClC;IACF;IACD,CACD,QAAQ,SAAS;AAChB,OAAI,CAAC,QAAQ,OAAO,KAAK,YAAY,YAAY,CAAC,KAAK,QAAQ,MAAM,EAAE;AACrE,WAAO,KACL,YAAY,MAAM,YAAY,UAAU,sBACzC;AACD,kBACE,gBACA,MAAM,YAAY,WAClB,qBACD;AACD,WAAO;;AAET,UAAO;IACP;AACJ,QAAM,KAAK,GAAG,UAAU,QAAQ,SAAS,SAAS,KAAK,CAAC;;AAI1D,KAAI,UAAU,mBAAmB;EAC/B,MAAM,yBAAyB,UAAU,kBACtC,KAAK,aAAa;GACjB,MAAM,UAAU,SAAS,SAAS;GAClC,MAAM,eAAe,YAAY,SAAS;AAG1C,OAAI,CAAC,gBAAgB,KACnB,KAAI;AACF,SAAK,MAAM,QAAQ;WACb;AACN,WAAO,KAAK,YAAY,aAAa,6BAA6B;AAClE,kBACE,gBACA,cACA,4BACD;AACD,WAAO;;GAIX,MAAM,aAAa,UACjB,SACA,UACA,SAAS,WAAW,EAAE,EACtB,SAAS,cACV;AAED,UAAO;IACL,QAAQ,eAAe,aAAa;IACpC,WAAW,cACT,YACA,oBAAoB,IAAI,SAAS,CAClC;IACD,SAAS;IACT,UAAU;IACV,YAAY;IACZ,GAAG,2BAA2B,UAAU,oBAAoB;IAC5D,YAAY;IACZ,QAAQ,SAAS;IAClB;IACD,CACD,QAAQ,SAAS;AAChB,OAAI,CAAC,KAAM,QAAO;AAClB,OAAI,OAAO,KAAK,YAAY,YAAY,CAAC,KAAK,QAAQ,MAAM,EAAE;AAC5D,WAAO,KAAK,YAAY,KAAK,SAAS,sBAAsB;AAC5D,kBAAc,gBAAgB,KAAK,UAAU,qBAAqB;AAClE,WAAO;;AAET,UAAO;IACP;AACJ,QAAM,KAAK,GAAG,uBAAuB,QAAQ,SAAS,SAAS,KAAK,CAAC;;AAGvE,MAAK,MAAM,YAAY,2BAA2B;AAChD,MACE,aAAa,UACb,aAAa,UACb,aAAa,oBAEb;AACF,MAAI,UAAU,WAAW;GACvB,MAAM,SAAS,UAAU,UACtB,KAAK,aAAa;IACjB,MAAM,UAAU,SAAS,SAAS;IAClC,MAAM,eAAe,YAAY,SAAS;IAE1C,MAAM,YAAY,kBAChB,SACA,cACA,UACA,SACD;AAED,QAAI,OAAO,cAAc,UAAU;AACjC,YAAO,KAAK,YAAY,aAAa,IAAI,UAAU,OAAO;AAC1D,mBAAc,gBAAgB,cAAc,UAAU,KAAK;AAC3D,YAAO;;AAGT,WAAO;KACL,SAAS;KACT,UAAU;KACV,YAAY,SAAS,aAAa;KAClC,GAAG,2BAA2B,UAAU,SAAS;KACjD,QAAQ,eAAe,aAAa;KACpC,WAAW,cACT,WACA,oBAAoB,IAAI,SAAS,CAClC;KACD,QAAQ,SAAS;KAClB;KACD,CACD,QAAQ,SAAS;AAChB,QACE,CAAC,QACD,OAAO,KAAK,YAAY,YACxB,CAAC,KAAK,QAAQ,MAAM,EACpB;AACA,YAAO,KACL,YAAY,MAAM,YAAY,UAAU,oCACzC;AACD,mBACE,gBACA,MAAM,YAAY,WAClB,mCACD;AACD,YAAO;;AAET,WAAO;KACP;AACJ,SAAM,KAAK,GAAG,OAAO,QAAQ,SAAS,SAAS,KAAK,CAAC;;;CAKzD,MAAM,eAAe,IAAI,IAAI,MAAM,KAAK,MAAM,EAAE,OAAO,CAAC;AACxD,MAAK,MAAM,UAAU,WAAW,MAAM,CACpC,KAAI,CAAC,aAAa,IAAI,OAAO,CAC3B,YAAW,OAAO,OAAO;AAI7B,QAAO;EAAE;EAAO;EAAY"}
|
|
1
|
+
{"version":3,"file":"aggregateFiles.js","names":[],"sources":["../../../src/formats/files/aggregateFiles.ts"],"sourcesContent":["import { logger } from '../../console/logger.js';\nimport { logErrorAndExit } from '../../console/logging.js';\nimport { lottieExpressionsError } from '../../console/index.js';\nimport { recordWarning } from '../../state/translateWarnings.js';\nimport { lottieHasExpressions } from './detectLottieExpressions.js';\nimport {\n getRelative,\n readFile,\n readBinaryFileBase64,\n} from '../../fs/findFilepath.js';\nimport { Settings } from '../../types/index.js';\nimport type { FileFormat, DataFormat, FileToUpload } from '../../types/data.js';\nimport { SUPPORTED_FILE_EXTENSIONS } from './supportedFiles.js';\nimport { parseJson } from '../json/parseJson.js';\nimport {\n resolveMintlifyRefs,\n shouldResolveRefs,\n} from '../../utils/resolveMintlifyRefs.js';\nimport { storeRefMap } from '../../state/mintlifyRefMap.js';\nimport parseYaml from '../yaml/parseYaml.js';\nimport { validateYamlSchema } from '../yaml/utils.js';\nimport { flattenJson } from '../json/flattenJson.js';\nimport type { JSONObject } from '../../types/data/json.js';\nimport YAML from 'yaml';\nimport { determineLibrary } from '../../fs/determineFramework/index.js';\nimport { hashStringSync, hashVersionId } from '../../utils/hash.js';\nimport { preprocessContent } from './preprocessContent.js';\nimport {\n parseKeyedMetadata,\n type KeyedMetadata,\n} from '../parseKeyedMetadata.js';\nimport { buildPublishMap } from '../../utils/resolvePublish.js';\nimport { getTransformFormatProperty } from './transformFormat.js';\n\n/**\n * Checks if a file path is a metadata companion file (e.g. foo.metadata.json)\n * AND its corresponding source file (e.g. foo.json) exists in the file list.\n * If both conditions are true, the metadata file should be skipped as a translation source.\n */\nfunction isCompanionMetadataFile(\n filePath: string,\n allFilePaths: string[]\n): boolean {\n const metadataPattern = /\\.metadata\\.(json|yaml|yml)$/;\n if (!metadataPattern.test(filePath)) return false;\n\n // Derive the source file path: foo.metadata.json -> foo.json\n const sourceFilePath = filePath.replace(\n /\\.metadata\\.(json|yaml|yml)$/,\n '.$1'\n );\n return allFilePaths.includes(sourceFilePath);\n}\n\nexport async function aggregateFiles(\n settings: Settings\n): Promise<{ files: FileToUpload[]; publishMap: Map<string, boolean> }> {\n // Aggregate all files to translate\n const files: FileToUpload[] = [];\n if (\n !settings.files ||\n (Object.keys(settings.files.placeholderPaths).length === 1 &&\n settings.files.placeholderPaths.gt)\n ) {\n return { files, publishMap: new Map<string, boolean>() };\n }\n\n const { resolvedPaths: filePaths } = settings.files;\n // Tolerate partially-constructed settings from programmatic callers\n const requiresReviewPaths =\n settings.files.requiresReviewPaths ?? new Set<string>();\n const skipValidation = settings.options?.skipFileValidation;\n\n // Build publish map upfront from resolved paths.\n const publishMap = buildPublishMap(filePaths, settings);\n\n // Process JSON files\n if (filePaths.json) {\n const { library, additionalModules } = determineLibrary();\n\n // Determine dataFormat for JSONs\n let dataFormat: DataFormat;\n if (library === 'next-intl') {\n dataFormat = 'ICU';\n } else if (library === 'i18next') {\n if (additionalModules.includes('i18next-icu')) {\n dataFormat = 'ICU';\n } else {\n dataFormat = 'I18NEXT';\n }\n } else {\n dataFormat = 'STRING';\n }\n\n const jsonFiles = filePaths.json\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.json!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n // Resolve $ref before parsing if configured\n let contentForParsing = content;\n if (shouldResolveRefs(filePath, settings.options)) {\n try {\n const json = JSON.parse(content);\n const { resolved, refMap } = resolveMintlifyRefs(json, filePath);\n storeRefMap(refMap);\n contentForParsing = JSON.stringify(resolved, null, 2);\n } catch {\n // JSON parse errors are handled below by parseJson\n }\n }\n\n const parsedJson = parseJson(\n contentForParsing,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n let parsedContent: JSONObject | undefined;\n try {\n parsedContent = JSON.parse(content) as JSONObject;\n } catch {\n // Content not parsable — skip metadata detection\n }\n if (parsedContent) {\n const rawMetadata = parseKeyedMetadata(filePath, parsedContent);\n if (rawMetadata) {\n // Run metadata through the same include/composite schema as the source\n // so key paths align at translation time\n const transformed = parseJson(\n JSON.stringify(rawMetadata),\n filePath,\n settings.options || {},\n settings.defaultLocale,\n false\n );\n const transformedMetadata = JSON.parse(transformed);\n\n // Filter metadata to only keep keys that exist in the transformed source\n // This prevents misaligned entries from wide JSONPath patterns\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedJson)));\n const filtered = Object.fromEntries(\n Object.entries(transformedMetadata).filter(([k]) =>\n sourceKeys.has(k)\n )\n ) as KeyedMetadata;\n\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the JSON schema — metadata was not attached`\n );\n }\n }\n }\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedJson,\n requiresReviewPaths.has(filePath)\n ),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'JSON' as const,\n ...getTransformFormatProperty(settings, 'json'),\n dataFormat,\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...jsonFiles.filter((file) => file !== null));\n }\n\n // Process YAML files\n if (filePaths.yaml) {\n const yamlFiles = filePaths.yaml\n .filter((filePath) => !isCompanionMetadataFile(filePath, filePaths.yaml!))\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate YAML parseability\n if (!skipValidation?.yaml) {\n try {\n YAML.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: YAML file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'YAML file is not parsable'\n );\n return null;\n }\n }\n\n const { content: parsedYaml, fileFormat } = parseYaml(\n content,\n filePath,\n settings.options || {}\n );\n\n // Detect companion metadata file\n let keyedMetadata: KeyedMetadata | undefined;\n try {\n const parsedYamlContent = YAML.parse(content);\n const rawMetadata = parseKeyedMetadata(filePath, parsedYamlContent);\n if (rawMetadata) {\n const yamlSchema = validateYamlSchema(\n settings.options || {},\n filePath\n );\n if (yamlSchema?.include) {\n // Flatten metadata through the same include schema as the source\n const flattened = flattenJson(rawMetadata, yamlSchema.include);\n // Filter to only keep keys that exist in the transformed source\n const sourceKeys = new Set(Object.keys(JSON.parse(parsedYaml)));\n const filtered = Object.fromEntries(\n Object.entries(flattened).filter(([k]) => sourceKeys.has(k))\n ) as KeyedMetadata;\n if (Object.keys(filtered).length > 0) {\n keyedMetadata = filtered;\n } else {\n logger.warn(\n `Companion metadata found for ${relativePath} but no keys aligned with the YAML schema — metadata was not attached`\n );\n }\n } else {\n keyedMetadata = rawMetadata;\n }\n }\n } catch {\n // Content not parsable as YAML — skip metadata detection\n }\n\n return {\n content: parsedYaml,\n fileName: relativePath,\n fileFormat,\n ...getTransformFormatProperty(settings, 'yaml'),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedYaml,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n ...(keyedMetadata && {\n formatMetadata: { keyedMetadata },\n }),\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file || typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: YAML file is empty`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'YAML file is empty'\n );\n return false;\n }\n return true;\n });\n files.push(...yamlFiles.filter((file) => file !== null));\n }\n\n // Process Twilio Content JSON files\n if (filePaths.twilioContentJson) {\n const twilioContentJsonFiles = filePaths.twilioContentJson\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n // Pre-validate JSON parseability\n if (!skipValidation?.json) {\n try {\n JSON.parse(content);\n } catch {\n logger.warn(`Skipping ${relativePath}: JSON file is not parsable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'JSON file is not parsable'\n );\n return null;\n }\n }\n\n const parsedJson = parseJson(\n content,\n filePath,\n settings.options || {},\n settings.defaultLocale\n );\n\n return {\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n parsedJson,\n requiresReviewPaths.has(filePath)\n ),\n content: parsedJson,\n fileName: relativePath,\n fileFormat: 'TWILIO_CONTENT_JSON' as const,\n ...getTransformFormatProperty(settings, 'twilioContentJson'),\n dataFormat: 'STRING' as const,\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (!file) return false;\n if (typeof file.content !== 'string' || !file.content.trim()) {\n logger.warn(`Skipping ${file.fileName}: JSON file is empty`);\n recordWarning('skipped_file', file.fileName, 'JSON file is empty');\n return false;\n }\n return true;\n });\n files.push(...twilioContentJsonFiles.filter((file) => file !== null));\n }\n\n // Process Lottie files (binary zip bundles). Content is read as raw bytes and\n // carried base64-encoded end-to-end; the downloaded translation is written\n // back as bytes. No text parsing/merge.\n if (filePaths.lottie) {\n // Lottie files carrying After Effects expressions (executable JS) are\n // rejected outright — collect every offender, then fail once with all of\n // them named rather than aborting on the first.\n const expressionOffenders: string[] = [];\n const lottieFiles = filePaths.lottie\n .map((filePath) => {\n const content = readBinaryFileBase64(filePath);\n const relativePath = getRelative(filePath);\n if (!content) {\n logger.warn(`Skipping ${relativePath}: file is empty or unreadable`);\n recordWarning(\n 'skipped_file',\n relativePath,\n 'File is empty or unreadable'\n );\n return null;\n }\n if (lottieHasExpressions(content)) {\n expressionOffenders.push(relativePath);\n return null;\n }\n return {\n content,\n fileName: relativePath,\n fileFormat: 'LOTTIE' as const,\n ...getTransformFormatProperty(settings, 'lottie'),\n fileId: hashStringSync(relativePath),\n versionId: hashStringSync(content),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => file !== null);\n if (expressionOffenders.length > 0) {\n logErrorAndExit(lottieExpressionsError(expressionOffenders));\n }\n files.push(...lottieFiles);\n }\n\n for (const fileType of SUPPORTED_FILE_EXTENSIONS) {\n if (\n fileType === 'json' ||\n fileType === 'yaml' ||\n fileType === 'twilioContentJson' ||\n fileType === 'lottie'\n )\n continue;\n if (filePaths[fileType]) {\n const parsed = filePaths[fileType]\n .map((filePath) => {\n const content = readFile(filePath);\n const relativePath = getRelative(filePath);\n\n const processed = preprocessContent(\n content,\n relativePath,\n fileType,\n settings\n );\n\n if (typeof processed !== 'string') {\n logger.warn(`Skipping ${relativePath}: ${processed.skip}`);\n recordWarning('skipped_file', relativePath, processed.skip);\n return null;\n }\n\n return {\n content: processed,\n fileName: relativePath,\n fileFormat: fileType.toUpperCase() as FileFormat,\n ...getTransformFormatProperty(settings, fileType),\n fileId: hashStringSync(relativePath),\n versionId: hashVersionId(\n processed,\n requiresReviewPaths.has(filePath)\n ),\n locale: settings.defaultLocale,\n } satisfies FileToUpload;\n })\n .filter((file) => {\n if (\n !file ||\n typeof file.content !== 'string' ||\n !file.content.trim()\n ) {\n logger.warn(\n `Skipping ${file?.fileName ?? 'unknown'}: File is empty after sanitization`\n );\n recordWarning(\n 'skipped_file',\n file?.fileName ?? 'unknown',\n 'File is empty after sanitization'\n );\n return false;\n }\n return true;\n });\n files.push(...parsed.filter((file) => file !== null));\n }\n }\n\n // Remove stale entries for files that were skipped during validation\n const validFileIds = new Set(files.map((f) => f.fileId));\n for (const fileId of publishMap.keys()) {\n if (!validFileIds.has(fileId)) {\n publishMap.delete(fileId);\n }\n }\n\n return { files, publishMap };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fg from "fast-glob";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
//#region src/formats/files/collectFonts.ts
|
|
5
|
+
async function collectFonts(settings) {
|
|
6
|
+
const fonts = settings.fonts;
|
|
7
|
+
if (!fonts?.include?.length) return [];
|
|
8
|
+
const cwd = settings.configDirectory ? path.dirname(settings.configDirectory) : process.cwd();
|
|
9
|
+
const matches = await fg(fonts.include, {
|
|
10
|
+
cwd,
|
|
11
|
+
ignore: fonts.exclude ?? [],
|
|
12
|
+
absolute: true,
|
|
13
|
+
onlyFiles: true,
|
|
14
|
+
unique: true
|
|
15
|
+
});
|
|
16
|
+
const uploads = [];
|
|
17
|
+
for (const absolutePath of matches) {
|
|
18
|
+
const bytes = await readFile(absolutePath);
|
|
19
|
+
uploads.push({
|
|
20
|
+
assetType: "FONT",
|
|
21
|
+
fileName: path.basename(absolutePath),
|
|
22
|
+
content: bytes.toString("base64")
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return uploads;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { collectFonts };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=collectFonts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collectFonts.js","names":[],"sources":["../../../src/formats/files/collectFonts.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport fg from 'fast-glob';\n\nimport type { Settings } from '../../types/index.js';\n\n// One font to upload to /v2/project/assets. Structurally matches the SDK's\n// AssetUpload; content is base64 (fonts are binary).\nexport type FontUpload = {\n assetType: 'FONT';\n fileName: string;\n content: string;\n};\n\n// Resolve the config's `fonts` globs to base64-encoded font uploads. Fonts are\n// locale-invariant, so there's no [locale] placeholder — just match the files\n// and read them as binary.\nexport async function collectFonts(settings: Settings): Promise<FontUpload[]> {\n const fonts = settings.fonts;\n if (!fonts?.include?.length) return [];\n\n // `files.include` globs resolve from the cwd generateSettings ran in, and\n // configDirectory is always `<cwd>/.gt` — so its parent is that same project\n // root. Globbing from configDirectory itself would search under `.gt/`.\n const cwd = settings.configDirectory\n ? path.dirname(settings.configDirectory)\n : process.cwd();\n const matches = await fg(fonts.include, {\n cwd,\n ignore: fonts.exclude ?? [],\n absolute: true,\n onlyFiles: true,\n unique: true,\n });\n\n const uploads: FontUpload[] = [];\n for (const absolutePath of matches) {\n const bytes = await readFile(absolutePath);\n uploads.push({\n assetType: 'FONT',\n fileName: path.basename(absolutePath),\n content: bytes.toString('base64'),\n });\n }\n return uploads;\n}\n"],"mappings":";;;;AAkBA,eAAsB,aAAa,UAA2C;CAC5E,MAAM,QAAQ,SAAS;AACvB,KAAI,CAAC,OAAO,SAAS,OAAQ,QAAO,EAAE;CAKtC,MAAM,MAAM,SAAS,kBACjB,KAAK,QAAQ,SAAS,gBAAgB,GACtC,QAAQ,KAAK;CACjB,MAAM,UAAU,MAAM,GAAG,MAAM,SAAS;EACtC;EACA,QAAQ,MAAM,WAAW,EAAE;EAC3B,UAAU;EACV,WAAW;EACX,QAAQ;EACT,CAAC;CAEF,MAAM,UAAwB,EAAE;AAChC,MAAK,MAAM,gBAAgB,SAAS;EAClC,MAAM,QAAQ,MAAM,SAAS,aAAa;AAC1C,UAAQ,KAAK;GACX,WAAW;GACX,UAAU,KAAK,SAAS,aAAa;GACrC,SAAS,MAAM,SAAS,SAAS;GAClC,CAAC;;AAEJ,QAAO"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true if a base64-encoded Lottie carries any After Effects expression
|
|
3
|
+
* (executable JavaScript). Handles both the dotLottie ZIP bundle (scans each
|
|
4
|
+
* `animations/*.json`) and a bare bodymovin JSON that uses the `.lottie`
|
|
5
|
+
* extension. Best-effort: an unreadable/non-Lottie payload returns false and is
|
|
6
|
+
* left to the existing upload validation.
|
|
7
|
+
*/
|
|
8
|
+
export declare function lottieHasExpressions(base64Content: string): boolean;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { unzipSync } from "fflate";
|
|
2
|
+
//#region src/formats/files/detectLottieExpressions.ts
|
|
3
|
+
function hasExpressionNode(node) {
|
|
4
|
+
if (Array.isArray(node)) {
|
|
5
|
+
for (const v of node) if (hasExpressionNode(v)) return true;
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (node !== null && typeof node === "object") {
|
|
9
|
+
const x = node.x;
|
|
10
|
+
if (typeof x === "string" && x.trim() !== "") return true;
|
|
11
|
+
for (const key in node) if (hasExpressionNode(node[key])) return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
function jsonHasExpression(text) {
|
|
16
|
+
try {
|
|
17
|
+
return hasExpressionNode(JSON.parse(text));
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns true if a base64-encoded Lottie carries any After Effects expression
|
|
24
|
+
* (executable JavaScript). Handles both the dotLottie ZIP bundle (scans each
|
|
25
|
+
* `animations/*.json`) and a bare bodymovin JSON that uses the `.lottie`
|
|
26
|
+
* extension. Best-effort: an unreadable/non-Lottie payload returns false and is
|
|
27
|
+
* left to the existing upload validation.
|
|
28
|
+
*/
|
|
29
|
+
function lottieHasExpressions(base64Content) {
|
|
30
|
+
const bytes = Buffer.from(base64Content, "base64");
|
|
31
|
+
try {
|
|
32
|
+
const entries = unzipSync(bytes, { filter: (file) => file.name.toLowerCase().endsWith(".json") });
|
|
33
|
+
for (const data of Object.values(entries)) if (jsonHasExpression(Buffer.from(data).toString("utf8"))) return true;
|
|
34
|
+
return false;
|
|
35
|
+
} catch {
|
|
36
|
+
return jsonHasExpression(bytes.toString("utf8"));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { lottieHasExpressions };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=detectLottieExpressions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detectLottieExpressions.js","names":[],"sources":["../../../src/formats/files/detectLottieExpressions.ts"],"sourcesContent":["import { unzipSync } from 'fflate';\n\nimport type { JSONValue } from '../../types/data/json.js';\n\n// After Effects \"expressions\" are JavaScript strings attached to an animated\n// property at key `x`; the full lottie-web build evaluates them with eval() at\n// render time, so a crafted .lottie is an arbitrary-code-execution vector. Until\n// the renderer strips/sandboxes them, we reject any Lottie that carries one.\n//\n// A split-dimension position ALSO uses `x`, but as an object/array (a nested\n// property) — never a string. So a non-empty string value at key `x` is the\n// unambiguous marker of an expression.\nfunction hasExpressionNode(node: JSONValue): boolean {\n if (Array.isArray(node)) {\n for (const v of node) if (hasExpressionNode(v)) return true;\n return false;\n }\n if (node !== null && typeof node === 'object') {\n const x = (node as { [key: string]: JSONValue }).x;\n if (typeof x === 'string' && x.trim() !== '') return true;\n for (const key in node) {\n if (hasExpressionNode((node as { [key: string]: JSONValue })[key])) {\n return true;\n }\n }\n }\n return false;\n}\n\nfunction jsonHasExpression(text: string): boolean {\n try {\n return hasExpressionNode(JSON.parse(text) as JSONValue);\n } catch {\n return false; // not JSON — nothing to scan\n }\n}\n\n/**\n * Returns true if a base64-encoded Lottie carries any After Effects expression\n * (executable JavaScript). Handles both the dotLottie ZIP bundle (scans each\n * `animations/*.json`) and a bare bodymovin JSON that uses the `.lottie`\n * extension. Best-effort: an unreadable/non-Lottie payload returns false and is\n * left to the existing upload validation.\n */\nexport function lottieHasExpressions(base64Content: string): boolean {\n const bytes = Buffer.from(base64Content, 'base64');\n try {\n // Only decompress JSON entries — skip embedded images/fonts/audio.\n const entries = unzipSync(bytes, {\n filter: (file) => file.name.toLowerCase().endsWith('.json'),\n });\n for (const data of Object.values(entries)) {\n if (jsonHasExpression(Buffer.from(data).toString('utf8'))) return true;\n }\n return false;\n } catch {\n // Not a valid ZIP — maybe a bare bodymovin JSON with a .lottie extension.\n return jsonHasExpression(bytes.toString('utf8'));\n }\n}\n"],"mappings":";;AAYA,SAAS,kBAAkB,MAA0B;AACnD,KAAI,MAAM,QAAQ,KAAK,EAAE;AACvB,OAAK,MAAM,KAAK,KAAM,KAAI,kBAAkB,EAAE,CAAE,QAAO;AACvD,SAAO;;AAET,KAAI,SAAS,QAAQ,OAAO,SAAS,UAAU;EAC7C,MAAM,IAAK,KAAsC;AACjD,MAAI,OAAO,MAAM,YAAY,EAAE,MAAM,KAAK,GAAI,QAAO;AACrD,OAAK,MAAM,OAAO,KAChB,KAAI,kBAAmB,KAAsC,KAAK,CAChE,QAAO;;AAIb,QAAO;;AAGT,SAAS,kBAAkB,MAAuB;AAChD,KAAI;AACF,SAAO,kBAAkB,KAAK,MAAM,KAAK,CAAc;SACjD;AACN,SAAO;;;;;;;;;;AAWX,SAAgB,qBAAqB,eAAgC;CACnE,MAAM,QAAQ,OAAO,KAAK,eAAe,SAAS;AAClD,KAAI;EAEF,MAAM,UAAU,UAAU,OAAO,EAC/B,SAAS,SAAS,KAAK,KAAK,aAAa,CAAC,SAAS,QAAQ,EAC5D,CAAC;AACF,OAAK,MAAM,QAAQ,OAAO,OAAO,QAAQ,CACvC,KAAI,kBAAkB,OAAO,KAAK,KAAK,CAAC,SAAS,OAAO,CAAC,CAAE,QAAO;AAEpE,SAAO;SACD;AAEN,SAAO,kBAAkB,MAAM,SAAS,OAAO,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SUPPORTED_FILE_EXTENSIONS: readonly ["json", "pot", "mdx", "md", "ts", "js", "yaml", "html", "txt", "twilioContentJson"];
|
|
1
|
+
export declare const SUPPORTED_FILE_EXTENSIONS: readonly ["json", "pot", "mdx", "md", "ts", "js", "yaml", "html", "txt", "twilioContentJson", "lottie"];
|
|
2
2
|
export declare const FILE_EXT_TO_EXT_LABEL: {
|
|
3
3
|
json: string;
|
|
4
4
|
pot: string;
|
|
@@ -10,4 +10,5 @@ export declare const FILE_EXT_TO_EXT_LABEL: {
|
|
|
10
10
|
html: string;
|
|
11
11
|
txt: string;
|
|
12
12
|
twilioContentJson: string;
|
|
13
|
+
lottie: string;
|
|
13
14
|
};
|
|
@@ -9,7 +9,8 @@ const SUPPORTED_FILE_EXTENSIONS = [
|
|
|
9
9
|
"yaml",
|
|
10
10
|
"html",
|
|
11
11
|
"txt",
|
|
12
|
-
"twilioContentJson"
|
|
12
|
+
"twilioContentJson",
|
|
13
|
+
"lottie"
|
|
13
14
|
];
|
|
14
15
|
const FILE_EXT_TO_EXT_LABEL = {
|
|
15
16
|
json: "JSON",
|
|
@@ -21,7 +22,8 @@ const FILE_EXT_TO_EXT_LABEL = {
|
|
|
21
22
|
yaml: "YAML",
|
|
22
23
|
html: "HTML",
|
|
23
24
|
txt: "Text",
|
|
24
|
-
twilioContentJson: "Twilio Content JSON"
|
|
25
|
+
twilioContentJson: "Twilio Content JSON",
|
|
26
|
+
lottie: "Lottie"
|
|
25
27
|
};
|
|
26
28
|
//#endregion
|
|
27
29
|
export { FILE_EXT_TO_EXT_LABEL, SUPPORTED_FILE_EXTENSIONS };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supportedFiles.js","names":[],"sources":["../../../src/formats/files/supportedFiles.ts"],"sourcesContent":["export const SUPPORTED_FILE_EXTENSIONS = [\n 'json',\n 'pot',\n 'mdx',\n 'md',\n 'ts',\n 'js',\n 'yaml',\n 'html',\n 'txt',\n 'twilioContentJson',\n] as const;\n\nexport const FILE_EXT_TO_EXT_LABEL = {\n json: 'JSON',\n pot: 'POT',\n mdx: 'MDX',\n md: 'Markdown',\n ts: 'TypeScript',\n js: 'JavaScript',\n yaml: 'YAML',\n html: 'HTML',\n txt: 'Text',\n twilioContentJson: 'Twilio Content JSON',\n};\n"],"mappings":";AAAA,MAAa,4BAA4B;CACvC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAa,wBAAwB;CACnC,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,mBAAmB;
|
|
1
|
+
{"version":3,"file":"supportedFiles.js","names":[],"sources":["../../../src/formats/files/supportedFiles.ts"],"sourcesContent":["export const SUPPORTED_FILE_EXTENSIONS = [\n 'json',\n 'pot',\n 'mdx',\n 'md',\n 'ts',\n 'js',\n 'yaml',\n 'html',\n 'txt',\n 'twilioContentJson',\n 'lottie',\n] as const;\n\nexport const FILE_EXT_TO_EXT_LABEL = {\n json: 'JSON',\n pot: 'POT',\n mdx: 'MDX',\n md: 'Markdown',\n ts: 'TypeScript',\n js: 'JavaScript',\n yaml: 'YAML',\n html: 'HTML',\n txt: 'Text',\n twilioContentJson: 'Twilio Content JSON',\n lottie: 'Lottie',\n};\n"],"mappings":";AAAA,MAAa,4BAA4B;CACvC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAa,wBAAwB;CACnC,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,mBAAmB;CACnB,QAAQ;CACT"}
|
|
@@ -14,6 +14,7 @@ export declare const CONFIG_FILE_TYPE_TO_FILE_FORMAT: {
|
|
|
14
14
|
readonly html: "HTML";
|
|
15
15
|
readonly txt: "TXT";
|
|
16
16
|
readonly twilioContentJson: "TWILIO_CONTENT_JSON";
|
|
17
|
+
readonly lottie: "LOTTIE";
|
|
17
18
|
};
|
|
18
19
|
/**
|
|
19
20
|
* Maps uppercase config aliases to the CLI's canonical lowercase file keys.
|
|
@@ -29,6 +30,7 @@ export declare const FILE_FORMAT_TO_CONFIG_FILE_TYPE: {
|
|
|
29
30
|
readonly HTML: "html";
|
|
30
31
|
readonly TXT: "txt";
|
|
31
32
|
readonly TWILIO_CONTENT_JSON: "twilioContentJson";
|
|
33
|
+
readonly LOTTIE: "lottie";
|
|
32
34
|
};
|
|
33
35
|
/**
|
|
34
36
|
* Converts uppercase file format config keys into the CLI's canonical lowercase keys.
|
|
@@ -13,7 +13,8 @@ const CONFIG_FILE_TYPE_TO_FILE_FORMAT = {
|
|
|
13
13
|
yaml: "YAML",
|
|
14
14
|
html: "HTML",
|
|
15
15
|
txt: "TXT",
|
|
16
|
-
twilioContentJson: "TWILIO_CONTENT_JSON"
|
|
16
|
+
twilioContentJson: "TWILIO_CONTENT_JSON",
|
|
17
|
+
lottie: "LOTTIE"
|
|
17
18
|
};
|
|
18
19
|
/**
|
|
19
20
|
* Maps uppercase config aliases to the CLI's canonical lowercase file keys.
|
|
@@ -28,7 +29,8 @@ const FILE_FORMAT_TO_CONFIG_FILE_TYPE = {
|
|
|
28
29
|
YAML: "yaml",
|
|
29
30
|
HTML: "html",
|
|
30
31
|
TXT: "txt",
|
|
31
|
-
TWILIO_CONTENT_JSON: "twilioContentJson"
|
|
32
|
+
TWILIO_CONTENT_JSON: "twilioContentJson",
|
|
33
|
+
LOTTIE: "lottie"
|
|
32
34
|
};
|
|
33
35
|
/**
|
|
34
36
|
* Maps API file format enum values to the extension the CLI should write.
|
|
@@ -45,7 +47,8 @@ const FILE_FORMAT_EXTENSIONS = {
|
|
|
45
47
|
JS: "js",
|
|
46
48
|
HTML: "html",
|
|
47
49
|
TXT: "txt",
|
|
48
|
-
TWILIO_CONTENT_JSON: "json"
|
|
50
|
+
TWILIO_CONTENT_JSON: "json",
|
|
51
|
+
LOTTIE: "lottie"
|
|
49
52
|
};
|
|
50
53
|
/**
|
|
51
54
|
* Converts uppercase file format config keys into the CLI's canonical lowercase keys.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformFormat.js","names":[],"sources":["../../../src/formats/files/transformFormat.ts"],"sourcesContent":["import type { FileFormat } from '../../types/data.js';\nimport type {\n FilesOptions,\n Settings,\n SupportedFileExtension,\n} from '../../types/index.js';\nimport { isSupportedFileFormatTransform } from 'generaltranslation/internal';\n\n/**\n * Maps CLI config file keys to API file format enum values.\n */\nexport const CONFIG_FILE_TYPE_TO_FILE_FORMAT = {\n json: 'JSON',\n pot: 'POT',\n mdx: 'MDX',\n md: 'MD',\n ts: 'TS',\n js: 'JS',\n yaml: 'YAML',\n html: 'HTML',\n txt: 'TXT',\n twilioContentJson: 'TWILIO_CONTENT_JSON',\n} as const satisfies Record<SupportedFileExtension, FileFormat>;\n\n/**\n * Maps uppercase config aliases to the CLI's canonical lowercase file keys.\n */\nexport const FILE_FORMAT_TO_CONFIG_FILE_TYPE = {\n JSON: 'json',\n POT: 'pot',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n YAML: 'yaml',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'twilioContentJson',\n} as const satisfies Partial<Record<FileFormat, SupportedFileExtension>>;\n\n/**\n * Maps API file format enum values to the extension the CLI should write.\n */\nconst FILE_FORMAT_EXTENSIONS = {\n GTJSON: 'json',\n JSON: 'json',\n PO: 'po',\n POT: 'pot',\n YAML: 'yaml',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'json',\n} as const satisfies Record<FileFormat, string>;\n\n/**\n * Converts uppercase file format config keys into the CLI's canonical lowercase keys.\n *\n * This lets users write either `files.POT` or `files.pot` while keeping the\n * rest of the CLI on its existing lowercase file-type convention.\n */\nexport function normalizeFilesOptions(files: FilesOptions): FilesOptions {\n const normalized = { ...files } as FilesOptions & Record<string, unknown>;\n\n for (const [fileFormat, fileType] of Object.entries(\n FILE_FORMAT_TO_CONFIG_FILE_TYPE\n ) as [string, SupportedFileExtension][]) {\n const uppercaseConfig = normalized[fileFormat] as\n | FilesOptions[SupportedFileExtension]\n | undefined;\n if (!normalized[fileType] && uppercaseConfig) {\n normalized[fileType] = uppercaseConfig;\n }\n delete normalized[fileFormat];\n }\n\n return normalized;\n}\n\n/**\n * Validates and resolves a configured output format for a source file type.\n *\n * Throws when the requested source -> output format is not supported by\n * `generaltranslation/internal`.\n */\nexport function resolveTransformationFormat(\n fileType: SupportedFileExtension,\n transformationFormat: string | undefined\n): FileFormat | undefined {\n if (!transformationFormat) return undefined;\n\n // Normalize to uppercase to match the FileFormat enum (e.g. \"po\" -> \"PO\")\n const normalized = transformationFormat.toUpperCase() as FileFormat;\n const fileFormat = CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType];\n\n if (!isSupportedFileFormatTransform(fileFormat, normalized)) {\n throw new Error(\n `Unsupported file format transform: ${fileFormat} -> ${normalized} in files.${fileType}. ` +\n `\"${normalized}\" is not a valid transformationFormat for ${fileFormat} source files.`\n );\n }\n\n return normalized;\n}\n\n/**\n * Returns the API upload/enqueue property for a file type when one is configured.\n */\nexport function getTransformFormatProperty(\n settings: Settings,\n fileType: SupportedFileExtension\n): { transformFormat?: FileFormat } {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return transformFormat ? { transformFormat } : {};\n}\n\n/**\n * Returns the preferred file extension for a translated file format.\n */\nexport function getFileExtensionForFormat(format: FileFormat): string {\n return FILE_FORMAT_EXTENSIONS[format];\n}\n\n/**\n * Rewrites a path's extension to match the translated file format.\n */\nexport function replaceFileExtensionForFormat(\n filePath: string,\n format: FileFormat\n): string {\n const extension = getFileExtensionForFormat(format);\n return /\\.[^/.]+$/.test(filePath)\n ? filePath.replace(/\\.[^/.]+$/, `.${extension}`)\n : `${filePath}.${extension}`;\n}\n\n/**\n * Detects whether any configured format transform changes the output file type.\n */\nexport function hasNonIdentityFileFormatTransform(settings: Settings): boolean {\n return Object.entries(settings.files?.transformFormats || {}).some(\n ([fileType, transformFormat]) =>\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType as SupportedFileExtension] !==\n transformFormat\n );\n}\n\n/**\n * Returns true when the configured transform for a file type changes its format.\n */\nexport function hasNonIdentityFileFormatTransformForType(\n settings: Settings,\n fileType: SupportedFileExtension\n): boolean {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return !!(\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType] !== transformFormat\n );\n}\n"],"mappings":";;;;;AAWA,MAAa,kCAAkC;CAC7C,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,mBAAmB;
|
|
1
|
+
{"version":3,"file":"transformFormat.js","names":[],"sources":["../../../src/formats/files/transformFormat.ts"],"sourcesContent":["import type { FileFormat } from '../../types/data.js';\nimport type {\n FilesOptions,\n Settings,\n SupportedFileExtension,\n} from '../../types/index.js';\nimport { isSupportedFileFormatTransform } from 'generaltranslation/internal';\n\n/**\n * Maps CLI config file keys to API file format enum values.\n */\nexport const CONFIG_FILE_TYPE_TO_FILE_FORMAT = {\n json: 'JSON',\n pot: 'POT',\n mdx: 'MDX',\n md: 'MD',\n ts: 'TS',\n js: 'JS',\n yaml: 'YAML',\n html: 'HTML',\n txt: 'TXT',\n twilioContentJson: 'TWILIO_CONTENT_JSON',\n lottie: 'LOTTIE',\n} as const satisfies Record<SupportedFileExtension, FileFormat>;\n\n/**\n * Maps uppercase config aliases to the CLI's canonical lowercase file keys.\n */\nexport const FILE_FORMAT_TO_CONFIG_FILE_TYPE = {\n JSON: 'json',\n POT: 'pot',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n YAML: 'yaml',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'twilioContentJson',\n LOTTIE: 'lottie',\n} as const satisfies Partial<Record<FileFormat, SupportedFileExtension>>;\n\n/**\n * Maps API file format enum values to the extension the CLI should write.\n */\nconst FILE_FORMAT_EXTENSIONS = {\n GTJSON: 'json',\n JSON: 'json',\n PO: 'po',\n POT: 'pot',\n YAML: 'yaml',\n MDX: 'mdx',\n MD: 'md',\n TS: 'ts',\n JS: 'js',\n HTML: 'html',\n TXT: 'txt',\n TWILIO_CONTENT_JSON: 'json',\n LOTTIE: 'lottie',\n} as const satisfies Record<FileFormat, string>;\n\n/**\n * Converts uppercase file format config keys into the CLI's canonical lowercase keys.\n *\n * This lets users write either `files.POT` or `files.pot` while keeping the\n * rest of the CLI on its existing lowercase file-type convention.\n */\nexport function normalizeFilesOptions(files: FilesOptions): FilesOptions {\n const normalized = { ...files } as FilesOptions & Record<string, unknown>;\n\n for (const [fileFormat, fileType] of Object.entries(\n FILE_FORMAT_TO_CONFIG_FILE_TYPE\n ) as [string, SupportedFileExtension][]) {\n const uppercaseConfig = normalized[fileFormat] as\n | FilesOptions[SupportedFileExtension]\n | undefined;\n if (!normalized[fileType] && uppercaseConfig) {\n normalized[fileType] = uppercaseConfig;\n }\n delete normalized[fileFormat];\n }\n\n return normalized;\n}\n\n/**\n * Validates and resolves a configured output format for a source file type.\n *\n * Throws when the requested source -> output format is not supported by\n * `generaltranslation/internal`.\n */\nexport function resolveTransformationFormat(\n fileType: SupportedFileExtension,\n transformationFormat: string | undefined\n): FileFormat | undefined {\n if (!transformationFormat) return undefined;\n\n // Normalize to uppercase to match the FileFormat enum (e.g. \"po\" -> \"PO\")\n const normalized = transformationFormat.toUpperCase() as FileFormat;\n const fileFormat = CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType];\n\n if (!isSupportedFileFormatTransform(fileFormat, normalized)) {\n throw new Error(\n `Unsupported file format transform: ${fileFormat} -> ${normalized} in files.${fileType}. ` +\n `\"${normalized}\" is not a valid transformationFormat for ${fileFormat} source files.`\n );\n }\n\n return normalized;\n}\n\n/**\n * Returns the API upload/enqueue property for a file type when one is configured.\n */\nexport function getTransformFormatProperty(\n settings: Settings,\n fileType: SupportedFileExtension\n): { transformFormat?: FileFormat } {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return transformFormat ? { transformFormat } : {};\n}\n\n/**\n * Returns the preferred file extension for a translated file format.\n */\nexport function getFileExtensionForFormat(format: FileFormat): string {\n return FILE_FORMAT_EXTENSIONS[format];\n}\n\n/**\n * Rewrites a path's extension to match the translated file format.\n */\nexport function replaceFileExtensionForFormat(\n filePath: string,\n format: FileFormat\n): string {\n const extension = getFileExtensionForFormat(format);\n return /\\.[^/.]+$/.test(filePath)\n ? filePath.replace(/\\.[^/.]+$/, `.${extension}`)\n : `${filePath}.${extension}`;\n}\n\n/**\n * Detects whether any configured format transform changes the output file type.\n */\nexport function hasNonIdentityFileFormatTransform(settings: Settings): boolean {\n return Object.entries(settings.files?.transformFormats || {}).some(\n ([fileType, transformFormat]) =>\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType as SupportedFileExtension] !==\n transformFormat\n );\n}\n\n/**\n * Returns true when the configured transform for a file type changes its format.\n */\nexport function hasNonIdentityFileFormatTransformForType(\n settings: Settings,\n fileType: SupportedFileExtension\n): boolean {\n const transformFormat = settings.files?.transformFormats?.[fileType];\n return !!(\n transformFormat &&\n CONFIG_FILE_TYPE_TO_FILE_FORMAT[fileType] !== transformFormat\n );\n}\n"],"mappings":";;;;;AAWA,MAAa,kCAAkC;CAC7C,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,mBAAmB;CACnB,QAAQ;CACT;;;;AAKD,MAAa,kCAAkC;CAC7C,MAAM;CACN,KAAK;CACL,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,MAAM;CACN,KAAK;CACL,qBAAqB;CACrB,QAAQ;CACT;;;;AAKD,MAAM,yBAAyB;CAC7B,QAAQ;CACR,MAAM;CACN,IAAI;CACJ,KAAK;CACL,MAAM;CACN,KAAK;CACL,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,KAAK;CACL,qBAAqB;CACrB,QAAQ;CACT;;;;;;;AAQD,SAAgB,sBAAsB,OAAmC;CACvE,MAAM,aAAa,EAAE,GAAG,OAAO;AAE/B,MAAK,MAAM,CAAC,YAAY,aAAa,OAAO,QAC1C,gCACD,EAAwC;EACvC,MAAM,kBAAkB,WAAW;AAGnC,MAAI,CAAC,WAAW,aAAa,gBAC3B,YAAW,YAAY;AAEzB,SAAO,WAAW;;AAGpB,QAAO;;;;;;;;AAST,SAAgB,4BACd,UACA,sBACwB;AACxB,KAAI,CAAC,qBAAsB,QAAO,KAAA;CAGlC,MAAM,aAAa,qBAAqB,aAAa;CACrD,MAAM,aAAa,gCAAgC;AAEnD,KAAI,CAAC,+BAA+B,YAAY,WAAW,CACzD,OAAM,IAAI,MACR,sCAAsC,WAAW,MAAM,WAAW,YAAY,SAAS,KACjF,WAAW,4CAA4C,WAAW,gBACzE;AAGH,QAAO;;;;;AAMT,SAAgB,2BACd,UACA,UACkC;CAClC,MAAM,kBAAkB,SAAS,OAAO,mBAAmB;AAC3D,QAAO,kBAAkB,EAAE,iBAAiB,GAAG,EAAE;;;;;AAMnD,SAAgB,0BAA0B,QAA4B;AACpE,QAAO,uBAAuB;;;;;AAMhC,SAAgB,8BACd,UACA,QACQ;CACR,MAAM,YAAY,0BAA0B,OAAO;AACnD,QAAO,YAAY,KAAK,SAAS,GAC7B,SAAS,QAAQ,aAAa,IAAI,YAAY,GAC9C,GAAG,SAAS,GAAG;;;;;AAMrB,SAAgB,kCAAkC,UAA6B;AAC7E,QAAO,OAAO,QAAQ,SAAS,OAAO,oBAAoB,EAAE,CAAC,CAAC,MAC3D,CAAC,UAAU,qBACV,mBACA,gCAAgC,cAC9B,gBACL;;;;;AAMH,SAAgB,yCACd,UACA,UACS;CACT,MAAM,kBAAkB,SAAS,OAAO,mBAAmB;AAC3D,QAAO,CAAC,EACN,mBACA,gCAAgC,cAAc"}
|
|
@@ -26,6 +26,13 @@ export declare function findFile(filePattern: string, file: string): string;
|
|
|
26
26
|
* @returns {string} - The contents of the file.
|
|
27
27
|
*/
|
|
28
28
|
export declare function readFile(filePath: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Read a file as raw bytes and return it base64-encoded. Used for binary
|
|
31
|
+
* formats (e.g. Lottie zip bundles) whose content must not be decoded as UTF-8.
|
|
32
|
+
* @param {string} filePath - The path to the file to read.
|
|
33
|
+
* @returns {string} - The base64-encoded contents, or '' if the file is absent.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readBinaryFileBase64(filePath: string): string;
|
|
29
36
|
/**
|
|
30
37
|
* Find a file in a directory.
|
|
31
38
|
* @param {string} dir - The directory to search in.
|
package/dist/fs/findFilepath.js
CHANGED
|
@@ -51,6 +51,16 @@ function readFile(filePath) {
|
|
|
51
51
|
return "";
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
+
* Read a file as raw bytes and return it base64-encoded. Used for binary
|
|
55
|
+
* formats (e.g. Lottie zip bundles) whose content must not be decoded as UTF-8.
|
|
56
|
+
* @param {string} filePath - The path to the file to read.
|
|
57
|
+
* @returns {string} - The base64-encoded contents, or '' if the file is absent.
|
|
58
|
+
*/
|
|
59
|
+
function readBinaryFileBase64(filePath) {
|
|
60
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) return fs.readFileSync(filePath).toString("base64");
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
54
64
|
* Find a file in a directory.
|
|
55
65
|
* @param {string} dir - The directory to search in.
|
|
56
66
|
* @param {string} file - The file to search for.
|
|
@@ -70,6 +80,6 @@ function getRelative(absolutePath) {
|
|
|
70
80
|
return path.relative(process.cwd(), path2);
|
|
71
81
|
}
|
|
72
82
|
//#endregion
|
|
73
|
-
export { findFilepath as default, findFile, findFileInDir, findFilepaths, getRelative, getRelativePath, readFile };
|
|
83
|
+
export { findFilepath as default, findFile, findFileInDir, findFilepaths, getRelative, getRelativePath, readBinaryFileBase64, readFile };
|
|
74
84
|
|
|
75
85
|
//# sourceMappingURL=findFilepath.js.map
|