From f889c017c54299326a30db6f5eb9269c81f5c6be Mon Sep 17 00:00:00 2001 From: "jason@dittowords.com" Date: Wed, 16 Apr 2025 14:17:44 -0700 Subject: [PATCH 1/2] Refactor output directory handling and enhance module type support in JSON formatters - Updated `JSONFormatter` and `BaseFramework` to use `outDir` instead of `outputDir` for improved consistency. - Introduced module type handling in `I18NextFramework` to support both CommonJS and ES module formats. - Added methods for generating require statements and module exports to streamline output generation. - Enhanced Zod schema for JSON output to include optional module type specification. --- lib/src/formatters/frameworks/json/base.ts | 2 +- lib/src/formatters/frameworks/json/i18next.ts | 47 ++++++++++++++++--- lib/src/formatters/json.ts | 4 +- .../mixins/javascriptCodegenMixin.ts | 8 ++++ lib/src/formatters/shared/base.ts | 4 +- lib/src/outputs/json.ts | 1 + 6 files changed, 54 insertions(+), 12 deletions(-) diff --git a/lib/src/formatters/frameworks/json/base.ts b/lib/src/formatters/frameworks/json/base.ts index 4430b56..df62aa8 100644 --- a/lib/src/formatters/frameworks/json/base.ts +++ b/lib/src/formatters/frameworks/json/base.ts @@ -8,7 +8,7 @@ export default class BaseFramework { constructor(output: Output) { this.output = output; - this.outDir = output.outDir ?? appContext.projectConfigDir; + this.outDir = output.outDir ?? appContext.outDir; } get framework() { diff --git a/lib/src/formatters/frameworks/json/i18next.ts b/lib/src/formatters/frameworks/json/i18next.ts index 54c7c57..7aac733 100644 --- a/lib/src/formatters/frameworks/json/i18next.ts +++ b/lib/src/formatters/frameworks/json/i18next.ts @@ -12,6 +12,11 @@ export default class I18NextFramework extends applyMixins( process( outputJsonFiles: Record> ) { + let moduleType: "commonjs" | "module" = "commonjs"; + if ("type" in this.output && this.output.type) { + moduleType = this.output.type; + } + const driverFile = new JavascriptOutputFile({ filename: "index", path: this.outDir, @@ -27,13 +32,23 @@ export default class I18NextFramework extends applyMixins( {} as Record ); - driverFile.content += this.generateImportStatements(outputJsonFiles); + if (moduleType === "module") { + driverFile.content += this.generateImportStatements(outputJsonFiles); - driverFile.content += `\n`; + driverFile.content += `\n`; - driverFile.content += this.generateDefaultExportString( - filesGroupedByVariantId - ); + driverFile.content += this.codegenDefaultExport( + this.generateExportedObjectString(filesGroupedByVariantId) + ); + } else { + driverFile.content += this.generateRequireStatements(outputJsonFiles); + + driverFile.content += `\n`; + + driverFile.content += this.codegenModuleExports( + this.generateExportedObjectString(filesGroupedByVariantId) + ); + } return [driverFile]; } @@ -56,12 +71,30 @@ export default class I18NextFramework extends applyMixins( return importStatements; } + /** + * Generates the require statements for the driver file. One require per generated json file. + * @param outputJsonFiles - The output json files. + * @returns The require statements, stringified. + */ + private generateRequireStatements( + outputJsonFiles: Record> + ) { + let requireStatements = ""; + for (const file of Object.values(outputJsonFiles)) { + requireStatements += this.codegenDefaultRequire( + this.sanitizeStringForJSVariableName(file.filename), + `./${file.filenameWithExtension}` + ); + } + return requireStatements; + } + /** * Generates the default export for the driver file. By default this is an object with the json imports grouped by variant id. * @param filesGroupedByVariantId - The files grouped by variant id. * @returns The default export, stringified. */ - private generateDefaultExportString( + private generateExportedObjectString( filesGroupedByVariantId: Record ) { const variantIds = Object.keys(filesGroupedByVariantId); @@ -85,6 +118,6 @@ export default class I18NextFramework extends applyMixins( defaultExportObjectString += `}`; - return this.codegenDefaultExport(defaultExportObjectString); + return defaultExportObjectString; } } diff --git a/lib/src/formatters/json.ts b/lib/src/formatters/json.ts index 14b3bfd..912b341 100644 --- a/lib/src/formatters/json.ts +++ b/lib/src/formatters/json.ts @@ -37,7 +37,7 @@ export default class JSONFormatter extends applyMixins( const variablesOutputFile = new JSONOutputFile({ filename: "variables", - path: this.outputDir, + path: this.outDir, }); for (let i = 0; i < data.textItems.length; i++) { @@ -47,7 +47,7 @@ export default class JSONFormatter extends applyMixins( outputJsonFiles[fileName] ??= new JSONOutputFile({ filename: fileName, - path: this.outputDir, + path: this.outDir, metadata: { variantId: textItem.variantId || "base" }, }); diff --git a/lib/src/formatters/mixins/javascriptCodegenMixin.ts b/lib/src/formatters/mixins/javascriptCodegenMixin.ts index f453c1e..54891f4 100644 --- a/lib/src/formatters/mixins/javascriptCodegenMixin.ts +++ b/lib/src/formatters/mixins/javascriptCodegenMixin.ts @@ -33,10 +33,18 @@ export default function javascriptCodegenMixin( return `import ${module} from "${moduleName}";\n`; } + protected codegenDefaultRequire(module: string, moduleName: string) { + return `const ${module} = require("${moduleName}");\n`; + } + protected codegenDefaultExport(module: string) { return `export default ${module};`; } + protected codegenModuleExports(module: string) { + return `module.exports = ${module};`; + } + protected codegenPad(depth: number) { return " ".repeat(depth * this.indentSpaces); } diff --git a/lib/src/formatters/shared/base.ts b/lib/src/formatters/shared/base.ts index abb9cc1..09fc6d2 100644 --- a/lib/src/formatters/shared/base.ts +++ b/lib/src/formatters/shared/base.ts @@ -8,12 +8,12 @@ import appContext from "../../utils/appContext"; export default class BaseFormatter { protected output: Output; protected projectConfig: ProjectConfigYAML; - protected outputDir: string; + protected outDir: string; constructor(output: Output, projectConfig: ProjectConfigYAML) { this.output = output; this.projectConfig = projectConfig; - this.outputDir = output.outDir ?? appContext.outDir; + this.outDir = output.outDir ?? appContext.outDir; } protected async fetchAPIData(): Promise { diff --git a/lib/src/outputs/json.ts b/lib/src/outputs/json.ts index b26342f..21edefd 100644 --- a/lib/src/outputs/json.ts +++ b/lib/src/outputs/json.ts @@ -8,6 +8,7 @@ const ZBaseJSONOutput = ZBaseOutputFilters.extend({ const Zi18NextJSONOutput = ZBaseJSONOutput.extend({ framework: z.literal("i18next"), + type: z.literal("module").or(z.literal("commonjs")).optional(), }); export const ZJSONOutput = z.discriminatedUnion("framework", [ From cf93c086f174160a417fd346bfb436748e27c39b Mon Sep 17 00:00:00 2001 From: Marla Hoggard Date: Thu, 17 Apr 2025 11:00:47 -0400 Subject: [PATCH 2/2] Suggestions from review: - Add docstring comments to codegen methods - Add type to some additional docstring comments - Rename one codegen method - Add named require method --- lib/src/formatters/frameworks/json/i18next.ts | 6 +- .../mixins/javascriptCodegenMixin.ts | 57 ++++++++++++++++++- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/lib/src/formatters/frameworks/json/i18next.ts b/lib/src/formatters/frameworks/json/i18next.ts index 7aac733..8f785cd 100644 --- a/lib/src/formatters/frameworks/json/i18next.ts +++ b/lib/src/formatters/frameworks/json/i18next.ts @@ -45,7 +45,7 @@ export default class I18NextFramework extends applyMixins( driverFile.content += `\n`; - driverFile.content += this.codegenModuleExports( + driverFile.content += this.codegenCommonJSModuleExports( this.generateExportedObjectString(filesGroupedByVariantId) ); } @@ -54,7 +54,7 @@ export default class I18NextFramework extends applyMixins( } /** - * Generates the import statements for the driver file. One import per generated json file. + * Generates the import statements for the driver file with type "module". One import per generated json file. * @param outputJsonFiles - The output json files. * @returns The import statements, stringified. */ @@ -72,7 +72,7 @@ export default class I18NextFramework extends applyMixins( } /** - * Generates the require statements for the driver file. One require per generated json file. + * Generates the require statements for the driver file with type "commonjs". One require per generated json file. * @param outputJsonFiles - The output json files. * @returns The require statements, stringified. */ diff --git a/lib/src/formatters/mixins/javascriptCodegenMixin.ts b/lib/src/formatters/mixins/javascriptCodegenMixin.ts index 54891f4..f2028ef 100644 --- a/lib/src/formatters/mixins/javascriptCodegenMixin.ts +++ b/lib/src/formatters/mixins/javascriptCodegenMixin.ts @@ -15,8 +15,13 @@ export default function javascriptCodegenMixin( return str.replace(/[^a-zA-Z0-9]/g, "_"); } - protected codegenNamedImport(modules: NamedImport[], moduleName: string) { - const formattedModules = modules + /** + * Converts an array of modules into a string that can be used for a named import or require statement. + * @param modules array of { name: string, alias?: string }, each named import + * @returns a string of comma-separated module names/aliases, sorted + */ + protected formatNamedModules(modules: NamedImport[]) { + return modules .map((m) => { if (m.alias) { return `${m.name} as ${m.alias}`; @@ -25,23 +30,69 @@ export default function javascriptCodegenMixin( }) .sort() .join(", "); + } + + /** + * Creates a named import statement for one or more items from a module. + * Used for i18next type "module". + * @param modules array of { name: string, alias?: string }, each named import + * @param moduleName the name of the file or package to import from + * @returns i.e `import { foo, bar as name } from "./file";` + */ + protected codegenNamedImport(modules: NamedImport[], moduleName: string) { + const formattedModules = this.formatNamedModules(modules); return `import { ${formattedModules} } from "${moduleName}";\n`; } + /** + * Creates a named require statement for one or more items from a module. + * Used for i18next type "commonjs". + * @param modules array of { name: string, alias?: string }, each named import + * @param moduleName the name of the file or package to import from + * @returns i.e `const { foo, bar as name } = require("./file");` + */ + protected codegenNamedRequire(modules: NamedImport[], moduleName: string) { + const formattedModules = this.formatNamedModules(modules); + + return `const { ${formattedModules} } = require("${moduleName}");\n`; + } + + /** + * Creates a default import statement for i18next type "module". + * @param module the name of the module to import + * @param moduleName the name of the file or package to import from + * @returns i.e codegenDefaultImport("item", "./file") => `import item from "./file";` + */ protected codegenDefaultImport(module: string, moduleName: string) { return `import ${module} from "${moduleName}";\n`; } + /** + * Creates a default require statement for i18next type "commonjs". + * @param module the name of the module to import + * @param moduleName the name of the file or package to import from + * @returns i.e codegenDefaultRequire("item", "./file") => `const item = require("./file)";` + */ protected codegenDefaultRequire(module: string, moduleName: string) { return `const ${module} = require("${moduleName}");\n`; } + /** + * Creates a default export statement for i18next type "module". + * @param module the name of the module to export + * @returns i.e codegenDefaultExport("item") => "export default item;" + */ protected codegenDefaultExport(module: string) { return `export default ${module};`; } - protected codegenModuleExports(module: string) { + /** + * Creates a module exports statement for i18next type "commonjs". + * @param module the name of the module to export + * @returns i.e codegenModuleExports("item") => "module.exports = item;" + */ + protected codegenCommonJSModuleExports(module: string) { return `module.exports = ${module};`; }