@elementor/extract-i18n-wordpress-expressions-webpack-plugin 0.2.2

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.
@@ -0,0 +1,30 @@
1
+ import { Compiler, Compilation, Chunk, Module as Module$1 } from 'webpack';
2
+
3
+ type Module = Module$1 & {
4
+ userRequest?: string;
5
+ rawRequest?: string;
6
+ _source?: {
7
+ _valueAsString?: string;
8
+ };
9
+ modules?: Module[];
10
+ };
11
+ type TranslationCallExpression = {
12
+ type: 'comment' | 'call-expression';
13
+ index: number;
14
+ value: string;
15
+ };
16
+ type TranslationCallExpressions = Map<string, TranslationCallExpression[]>;
17
+ declare class ExtractI18nWordpressExpressionsWebpackPlugin {
18
+ apply(compiler: Compiler): void;
19
+ getModuleExpressionsMap(compilation: Compilation): Map<any, any>;
20
+ createTranslationsFiles(compilation: Compilation, translationCallExpressions: TranslationCallExpressions): Promise<void[]>;
21
+ getSubModulesToCheck(module: Module): Module[];
22
+ getFileFromChunk(chunk: Chunk): string | undefined;
23
+ shouldCheckModule(module: Module): boolean;
24
+ findMainModuleOfEntry(module: Module, compilation: Compilation): string | null;
25
+ extractExpressionsFromSubmodule(subModule: Module): TranslationCallExpression[];
26
+ generateTranslationFilename(basePath: string, filename: string): string;
27
+ generateTranslationFileContent(expressions: TranslationCallExpression[]): string;
28
+ }
29
+
30
+ export { ExtractI18nWordpressExpressionsWebpackPlugin };
@@ -0,0 +1,30 @@
1
+ import { Compiler, Compilation, Chunk, Module as Module$1 } from 'webpack';
2
+
3
+ type Module = Module$1 & {
4
+ userRequest?: string;
5
+ rawRequest?: string;
6
+ _source?: {
7
+ _valueAsString?: string;
8
+ };
9
+ modules?: Module[];
10
+ };
11
+ type TranslationCallExpression = {
12
+ type: 'comment' | 'call-expression';
13
+ index: number;
14
+ value: string;
15
+ };
16
+ type TranslationCallExpressions = Map<string, TranslationCallExpression[]>;
17
+ declare class ExtractI18nWordpressExpressionsWebpackPlugin {
18
+ apply(compiler: Compiler): void;
19
+ getModuleExpressionsMap(compilation: Compilation): Map<any, any>;
20
+ createTranslationsFiles(compilation: Compilation, translationCallExpressions: TranslationCallExpressions): Promise<void[]>;
21
+ getSubModulesToCheck(module: Module): Module[];
22
+ getFileFromChunk(chunk: Chunk): string | undefined;
23
+ shouldCheckModule(module: Module): boolean;
24
+ findMainModuleOfEntry(module: Module, compilation: Compilation): string | null;
25
+ extractExpressionsFromSubmodule(subModule: Module): TranslationCallExpression[];
26
+ generateTranslationFilename(basePath: string, filename: string): string;
27
+ generateTranslationFileContent(expressions: TranslationCallExpression[]): string;
28
+ }
29
+
30
+ export { ExtractI18nWordpressExpressionsWebpackPlugin };
package/dist/index.js ADDED
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ ExtractI18nWordpressExpressionsWebpackPlugin: () => ExtractI18nWordpressExpressionsWebpackPlugin
34
+ });
35
+ module.exports = __toCommonJS(src_exports);
36
+ var fs = __toESM(require("fs"));
37
+ var path = __toESM(require("path"));
38
+ var MODULE_FILTERS = Object.freeze([/(([^!?\s]+?)(?:\.js|\.jsx|\.ts|\.tsx))$/, /^((?!node_modules).)*$/]);
39
+ var COMMENTS_REGEXPS = Object.freeze([
40
+ // Matches translators comment block: `/* translators: %s */`.
41
+ /\/\*[\t ]*translators:.*\*\//gm,
42
+ /// Matches translators inline comment: `// translators: %s`.
43
+ /(\/\/)[\t ]*translators:[^\r\n]*/gm
44
+ ]);
45
+ var TRANSLATIONS_REGEXPS = Object.freeze([
46
+ // Matches translation functions: `__('Hello', 'elementor')`, `_n('Me', 'Us', 2, 'elementor-pro')`.
47
+ /\b_(?:_|n|nx|x)\(.*?,\s*(?<c>['"`])[\w-]+\k<c>\)/gm
48
+ ]);
49
+ var ExtractI18nWordpressExpressionsWebpackPlugin = class {
50
+ apply(compiler) {
51
+ let translationCallExpressions = /* @__PURE__ */ new Map();
52
+ compiler.hooks.thisCompilation.tap(this.constructor.name, (compilation) => {
53
+ compilation.hooks.processAssets.tap({ name: this.constructor.name }, () => {
54
+ translationCallExpressions = this.getModuleExpressionsMap(compilation);
55
+ });
56
+ });
57
+ compiler.hooks.afterEmit.tapPromise(this.constructor.name, async (compilation) => {
58
+ await this.createTranslationsFiles(compilation, translationCallExpressions);
59
+ });
60
+ }
61
+ getModuleExpressionsMap(compilation) {
62
+ const moduleExpressionsMap = /* @__PURE__ */ new Map();
63
+ [...compilation.chunks].forEach((chunk) => {
64
+ const chunkJSFile = this.getFileFromChunk(chunk);
65
+ if (!chunkJSFile) {
66
+ return;
67
+ }
68
+ compilation.chunkGraph.getChunkModules(chunk).forEach((module2) => {
69
+ this.getSubModulesToCheck(module2).forEach((subModule) => {
70
+ const mainEntryFile = this.findMainModuleOfEntry(subModule, compilation);
71
+ if (!moduleExpressionsMap.has(mainEntryFile)) {
72
+ moduleExpressionsMap.set(mainEntryFile, []);
73
+ }
74
+ this.extractExpressionsFromSubmodule(subModule).forEach((expression) => {
75
+ moduleExpressionsMap.get(mainEntryFile).push(expression);
76
+ });
77
+ });
78
+ });
79
+ });
80
+ return moduleExpressionsMap;
81
+ }
82
+ async createTranslationsFiles(compilation, translationCallExpressions) {
83
+ const promises2 = [...compilation.entrypoints].map(([id, entrypoint]) => {
84
+ const chunk = entrypoint.chunks.find(({ name }) => name === id);
85
+ if (!chunk) {
86
+ return Promise.resolve();
87
+ }
88
+ const chunkJSFile = this.getFileFromChunk(chunk);
89
+ if (!chunkJSFile) {
90
+ return Promise.resolve();
91
+ }
92
+ const { entry } = compilation.options;
93
+ if (!entry || typeof entry !== "object" || !(id in entry)) {
94
+ throw new Error(`Entry must be an object. e.g: {app: './path/to/app.js'}`);
95
+ }
96
+ const mainFilePath = entry[id].import?.[0];
97
+ if (!mainFilePath) {
98
+ throw new Error("Entry is invalid");
99
+ }
100
+ const { path: outputPath } = compilation.options.output;
101
+ if (!outputPath) {
102
+ throw new Error("Output path is invalid");
103
+ }
104
+ const assetFilename = this.generateTranslationFilename(
105
+ outputPath,
106
+ compilation.getPath("[file]", { filename: chunkJSFile })
107
+ );
108
+ const assetFileContent = this.generateTranslationFileContent(
109
+ translationCallExpressions.get(mainFilePath) || []
110
+ );
111
+ return fs.promises.writeFile(assetFilename, assetFileContent);
112
+ });
113
+ return await Promise.all(promises2);
114
+ }
115
+ getSubModulesToCheck(module2) {
116
+ return [...module2.modules || [], module2].filter((subModule) => this.shouldCheckModule(subModule));
117
+ }
118
+ getFileFromChunk(chunk) {
119
+ return [...chunk.files].find((f) => /\.js$/i.test(f));
120
+ }
121
+ shouldCheckModule(module2) {
122
+ return MODULE_FILTERS.every((filter) => filter.test(module2.userRequest || ""));
123
+ }
124
+ findMainModuleOfEntry(module2, compilation) {
125
+ const issuer = compilation.moduleGraph.getIssuer(module2);
126
+ if (issuer) {
127
+ return this.findMainModuleOfEntry(issuer, compilation);
128
+ }
129
+ return module2.rawRequest || null;
130
+ }
131
+ extractExpressionsFromSubmodule(subModule) {
132
+ const source = subModule?._source?._valueAsString;
133
+ if (!source) {
134
+ return [];
135
+ }
136
+ const translationCallExpressions = [];
137
+ [
138
+ ...TRANSLATIONS_REGEXPS,
139
+ ...COMMENTS_REGEXPS
140
+ ].forEach((regexp) => {
141
+ [...source.matchAll(regexp)].forEach((res) => {
142
+ translationCallExpressions.push({
143
+ type: COMMENTS_REGEXPS.includes(regexp) ? "comment" : "call-expression",
144
+ index: res.index || 0,
145
+ value: res[0]
146
+ });
147
+ });
148
+ });
149
+ return translationCallExpressions;
150
+ }
151
+ generateTranslationFilename(basePath, filename) {
152
+ return path.join(
153
+ basePath,
154
+ filename.replace(/(\.min)?\.js$/i, ".strings.js")
155
+ );
156
+ }
157
+ generateTranslationFileContent(expressions) {
158
+ return expressions.sort((a, b) => a.index - b.index).map((expr) => `${expr.value}${expr.type === "comment" ? "" : ";"}`).join("\n");
159
+ }
160
+ };
161
+ // Annotate the CommonJS export names for ESM import in node:
162
+ 0 && (module.exports = {
163
+ ExtractI18nWordpressExpressionsWebpackPlugin
164
+ });
165
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { Compilation, Compiler, Chunk, Module as WebpackModule } from 'webpack';\n\ntype Module = WebpackModule & {\n\tuserRequest?: string;\n\trawRequest?: string;\n\t_source?: {\n\t\t_valueAsString?: string;\n\t},\n\tmodules?: Module[];\n}\n\ntype TranslationCallExpression = {\n\ttype: 'comment' | 'call-expression';\n\tindex: number;\n\tvalue: string;\n};\n\ntype TranslationCallExpressions = Map<string, TranslationCallExpression[]>;\n\nconst MODULE_FILTERS = Object.freeze( [ /(([^!?\\s]+?)(?:\\.js|\\.jsx|\\.ts|\\.tsx))$/, /^((?!node_modules).)*$/ ] );\n\nconst COMMENTS_REGEXPS = Object.freeze( [\n\t// Matches translators comment block: `/* translators: %s */`.\n\t/\\/\\*[\\t ]*translators:.*\\*\\//gm,\n\t/// Matches translators inline comment: `// translators: %s`.\n\t/(\\/\\/)[\\t ]*translators:[^\\r\\n]*/gm,\n] );\n\nconst TRANSLATIONS_REGEXPS = Object.freeze( [\n\t// Matches translation functions: `__('Hello', 'elementor')`, `_n('Me', 'Us', 2, 'elementor-pro')`.\n\t/\\b_(?:_|n|nx|x)\\(.*?,\\s*(?<c>['\"`])[\\w-]+\\k<c>\\)/gm,\n] );\n\nexport class ExtractI18nWordpressExpressionsWebpackPlugin {\n\tapply( compiler: Compiler ) {\n\t\t// Learn more about Webpack plugin system: https://webpack.js.org/api/plugins/\n\n\t\tlet translationCallExpressions: TranslationCallExpressions = new Map();\n\n\t\t// Learn more about Webpack compilation process and hooks: https://webpack.js.org/api/compilation-hooks/\n\t\tcompiler.hooks.thisCompilation.tap( this.constructor.name, ( compilation ) => {\n\t\t\t// We tap into the time that Webpack has finished processing all the other assets\n\t\t\t// learn more: https://webpack.js.org/api/compilation-hooks/#processassets.\n\t\t\tcompilation.hooks.processAssets.tap( { name: this.constructor.name }, () => {\n\t\t\t\ttranslationCallExpressions = this.getModuleExpressionsMap( compilation );\n\t\t\t} );\n\t\t} );\n\n\t\tcompiler.hooks.afterEmit.tapPromise( this.constructor.name, async ( compilation ) => {\n\t\t\t// Create all the translations files based on the call expressions.\n\t\t\tawait this.createTranslationsFiles( compilation, translationCallExpressions );\n\t\t} );\n\t}\n\n\tgetModuleExpressionsMap( compilation: Compilation ) {\n\t\tconst moduleExpressionsMap = new Map();\n\n\t\t[ ...compilation.chunks ].forEach( ( chunk ) => {\n\t\t\tconst chunkJSFile = this.getFileFromChunk( chunk );\n\n\t\t\tif ( ! chunkJSFile ) {\n\t\t\t\t// There's no JS file in this chunk, no work for us. Typically a `style.css` from cache group.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcompilation.chunkGraph.getChunkModules( chunk ).forEach( ( module ) => {\n\t\t\t\tthis.getSubModulesToCheck( module ).forEach( ( subModule ) => {\n\t\t\t\t\tconst mainEntryFile = this.findMainModuleOfEntry( subModule, compilation );\n\n\t\t\t\t\tif ( ! moduleExpressionsMap.has( mainEntryFile ) ) {\n\t\t\t\t\t\tmoduleExpressionsMap.set( mainEntryFile, [] );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Running over the submodules and find all the translation call expressions and their translators comment\n\t\t\t\t\t// (e.g `/* translators: %s: name*/ __('Hello %s', 'elementor')`),\n\t\t\t\t\t// extract them and add them to a Map, where the key is the main entry file, and the value is an array of all the\n\t\t\t\t\t// translation call expressions.\n\t\t\t\t\tthis.extractExpressionsFromSubmodule( subModule ).forEach( ( expression ) => {\n\t\t\t\t\t\tmoduleExpressionsMap.get( mainEntryFile ).push( expression );\n\t\t\t\t\t} );\n\t\t\t\t} );\n\t\t\t} );\n\t\t} );\n\n\t\treturn moduleExpressionsMap;\n\t}\n\n\tasync createTranslationsFiles( compilation: Compilation, translationCallExpressions: TranslationCallExpressions ) {\n\t\tconst promises = [ ...compilation.entrypoints ].map( ( [ id, entrypoint ] ) => {\n\t\t\tconst chunk = entrypoint.chunks.find( ( { name } ) => name === id );\n\n\t\t\tif ( ! chunk ) {\n\t\t\t\treturn Promise.resolve();\n\t\t\t}\n\n\t\t\tconst chunkJSFile = this.getFileFromChunk( chunk );\n\n\t\t\tif ( ! chunkJSFile ) {\n\t\t\t\treturn Promise.resolve();\n\t\t\t}\n\n\t\t\tconst { entry } = compilation.options;\n\n\t\t\tif ( ! entry || typeof entry !== 'object' || ! ( id in entry ) ) {\n\t\t\t\tthrow new Error( `Entry must be an object. e.g: {app: './path/to/app.js'}` );\n\t\t\t}\n\n\t\t\tconst mainFilePath = entry[ id ].import?.[ 0 ];\n\n\t\t\tif ( ! mainFilePath ) {\n\t\t\t\tthrow new Error( 'Entry is invalid' );\n\t\t\t}\n\n\t\t\tconst { path: outputPath } = compilation.options.output;\n\n\t\t\tif ( ! outputPath ) {\n\t\t\t\tthrow new Error( 'Output path is invalid' );\n\t\t\t}\n\n\t\t\tconst assetFilename = this.generateTranslationFilename(\n\t\t\t\toutputPath,\n\t\t\t\tcompilation.getPath( '[file]', { filename: chunkJSFile } )\n\t\t\t);\n\n\t\t\tconst assetFileContent = this.generateTranslationFileContent(\n\t\t\t\ttranslationCallExpressions.get( mainFilePath ) || []\n\t\t\t);\n\n\t\t\treturn fs.promises.writeFile( assetFilename, assetFileContent );\n\t\t} );\n\n\t\treturn await Promise.all( promises );\n\t}\n\n\tgetSubModulesToCheck( module: Module ) {\n\t\treturn [ ...( module.modules || [] ), module ]\n\t\t\t.filter( ( subModule ) => this.shouldCheckModule( subModule ) );\n\t}\n\n\tgetFileFromChunk( chunk: Chunk ) {\n\t\treturn [ ...chunk.files ].find( ( f ) => /\\.js$/i.test( f ) );\n\t}\n\n\tshouldCheckModule( module: Module ) {\n\t\treturn MODULE_FILTERS.every( ( filter ) => filter.test( module.userRequest || '' ) );\n\t}\n\n\tfindMainModuleOfEntry( module: Module, compilation: Compilation ) : string | null {\n\t\tconst issuer = compilation.moduleGraph.getIssuer( module );\n\n\t\tif ( issuer ) {\n\t\t\treturn this.findMainModuleOfEntry( issuer, compilation );\n\t\t}\n\n\t\treturn module.rawRequest || null;\n\t}\n\n\textractExpressionsFromSubmodule( subModule: Module ) {\n\t\tconst source = subModule?._source?._valueAsString;\n\n\t\tif ( ! source ) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst translationCallExpressions: TranslationCallExpression[] = [];\n\n\t\t[\n\t\t\t...TRANSLATIONS_REGEXPS,\n\t\t\t...COMMENTS_REGEXPS,\n\t\t].forEach( ( regexp ) => {\n\t\t\t[ ...source.matchAll( regexp ) ].forEach( ( res ) => {\n\t\t\t\ttranslationCallExpressions.push( {\n\t\t\t\t\ttype: COMMENTS_REGEXPS.includes( regexp ) ? 'comment' : 'call-expression',\n\t\t\t\t\tindex: res.index || 0,\n\t\t\t\t\tvalue: res[ 0 ],\n\t\t\t\t} );\n\t\t\t} );\n\t\t} );\n\n\t\treturn translationCallExpressions;\n\t}\n\n\tgenerateTranslationFilename( basePath: string, filename: string ) {\n\t\treturn path.join(\n\t\t\tbasePath,\n\t\t\tfilename.replace( /(\\.min)?\\.js$/i, '.strings.js' )\n\t\t);\n\t}\n\n\tgenerateTranslationFileContent( expressions: TranslationCallExpression[] ) {\n\t\treturn expressions\n\t\t\t// Sort by the index it was found in the file based on the regexp (and not by the order it was added to the array).\n\t\t\t.sort( ( a, b ) => a.index - b.index )\n\t\t\t// Add a semicolon when needed.\n\t\t\t.map( ( expr ) => `${ expr.value }${ expr.type === 'comment' ? '' : ';' }` )\n\t\t\t// Join all the expressions to a single string with line-breaks between them.\n\t\t\t.join( '\\n' );\n\t}\n}\n"],"mappings":"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","names":["module","promises"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,130 @@
1
+ // src/index.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ var MODULE_FILTERS = Object.freeze([/(([^!?\s]+?)(?:\.js|\.jsx|\.ts|\.tsx))$/, /^((?!node_modules).)*$/]);
5
+ var COMMENTS_REGEXPS = Object.freeze([
6
+ // Matches translators comment block: `/* translators: %s */`.
7
+ /\/\*[\t ]*translators:.*\*\//gm,
8
+ /// Matches translators inline comment: `// translators: %s`.
9
+ /(\/\/)[\t ]*translators:[^\r\n]*/gm
10
+ ]);
11
+ var TRANSLATIONS_REGEXPS = Object.freeze([
12
+ // Matches translation functions: `__('Hello', 'elementor')`, `_n('Me', 'Us', 2, 'elementor-pro')`.
13
+ /\b_(?:_|n|nx|x)\(.*?,\s*(?<c>['"`])[\w-]+\k<c>\)/gm
14
+ ]);
15
+ var ExtractI18nWordpressExpressionsWebpackPlugin = class {
16
+ apply(compiler) {
17
+ let translationCallExpressions = /* @__PURE__ */ new Map();
18
+ compiler.hooks.thisCompilation.tap(this.constructor.name, (compilation) => {
19
+ compilation.hooks.processAssets.tap({ name: this.constructor.name }, () => {
20
+ translationCallExpressions = this.getModuleExpressionsMap(compilation);
21
+ });
22
+ });
23
+ compiler.hooks.afterEmit.tapPromise(this.constructor.name, async (compilation) => {
24
+ await this.createTranslationsFiles(compilation, translationCallExpressions);
25
+ });
26
+ }
27
+ getModuleExpressionsMap(compilation) {
28
+ const moduleExpressionsMap = /* @__PURE__ */ new Map();
29
+ [...compilation.chunks].forEach((chunk) => {
30
+ const chunkJSFile = this.getFileFromChunk(chunk);
31
+ if (!chunkJSFile) {
32
+ return;
33
+ }
34
+ compilation.chunkGraph.getChunkModules(chunk).forEach((module) => {
35
+ this.getSubModulesToCheck(module).forEach((subModule) => {
36
+ const mainEntryFile = this.findMainModuleOfEntry(subModule, compilation);
37
+ if (!moduleExpressionsMap.has(mainEntryFile)) {
38
+ moduleExpressionsMap.set(mainEntryFile, []);
39
+ }
40
+ this.extractExpressionsFromSubmodule(subModule).forEach((expression) => {
41
+ moduleExpressionsMap.get(mainEntryFile).push(expression);
42
+ });
43
+ });
44
+ });
45
+ });
46
+ return moduleExpressionsMap;
47
+ }
48
+ async createTranslationsFiles(compilation, translationCallExpressions) {
49
+ const promises2 = [...compilation.entrypoints].map(([id, entrypoint]) => {
50
+ const chunk = entrypoint.chunks.find(({ name }) => name === id);
51
+ if (!chunk) {
52
+ return Promise.resolve();
53
+ }
54
+ const chunkJSFile = this.getFileFromChunk(chunk);
55
+ if (!chunkJSFile) {
56
+ return Promise.resolve();
57
+ }
58
+ const { entry } = compilation.options;
59
+ if (!entry || typeof entry !== "object" || !(id in entry)) {
60
+ throw new Error(`Entry must be an object. e.g: {app: './path/to/app.js'}`);
61
+ }
62
+ const mainFilePath = entry[id].import?.[0];
63
+ if (!mainFilePath) {
64
+ throw new Error("Entry is invalid");
65
+ }
66
+ const { path: outputPath } = compilation.options.output;
67
+ if (!outputPath) {
68
+ throw new Error("Output path is invalid");
69
+ }
70
+ const assetFilename = this.generateTranslationFilename(
71
+ outputPath,
72
+ compilation.getPath("[file]", { filename: chunkJSFile })
73
+ );
74
+ const assetFileContent = this.generateTranslationFileContent(
75
+ translationCallExpressions.get(mainFilePath) || []
76
+ );
77
+ return fs.promises.writeFile(assetFilename, assetFileContent);
78
+ });
79
+ return await Promise.all(promises2);
80
+ }
81
+ getSubModulesToCheck(module) {
82
+ return [...module.modules || [], module].filter((subModule) => this.shouldCheckModule(subModule));
83
+ }
84
+ getFileFromChunk(chunk) {
85
+ return [...chunk.files].find((f) => /\.js$/i.test(f));
86
+ }
87
+ shouldCheckModule(module) {
88
+ return MODULE_FILTERS.every((filter) => filter.test(module.userRequest || ""));
89
+ }
90
+ findMainModuleOfEntry(module, compilation) {
91
+ const issuer = compilation.moduleGraph.getIssuer(module);
92
+ if (issuer) {
93
+ return this.findMainModuleOfEntry(issuer, compilation);
94
+ }
95
+ return module.rawRequest || null;
96
+ }
97
+ extractExpressionsFromSubmodule(subModule) {
98
+ const source = subModule?._source?._valueAsString;
99
+ if (!source) {
100
+ return [];
101
+ }
102
+ const translationCallExpressions = [];
103
+ [
104
+ ...TRANSLATIONS_REGEXPS,
105
+ ...COMMENTS_REGEXPS
106
+ ].forEach((regexp) => {
107
+ [...source.matchAll(regexp)].forEach((res) => {
108
+ translationCallExpressions.push({
109
+ type: COMMENTS_REGEXPS.includes(regexp) ? "comment" : "call-expression",
110
+ index: res.index || 0,
111
+ value: res[0]
112
+ });
113
+ });
114
+ });
115
+ return translationCallExpressions;
116
+ }
117
+ generateTranslationFilename(basePath, filename) {
118
+ return path.join(
119
+ basePath,
120
+ filename.replace(/(\.min)?\.js$/i, ".strings.js")
121
+ );
122
+ }
123
+ generateTranslationFileContent(expressions) {
124
+ return expressions.sort((a, b) => a.index - b.index).map((expr) => `${expr.value}${expr.type === "comment" ? "" : ";"}`).join("\n");
125
+ }
126
+ };
127
+ export {
128
+ ExtractI18nWordpressExpressionsWebpackPlugin
129
+ };
130
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { Compilation, Compiler, Chunk, Module as WebpackModule } from 'webpack';\n\ntype Module = WebpackModule & {\n\tuserRequest?: string;\n\trawRequest?: string;\n\t_source?: {\n\t\t_valueAsString?: string;\n\t},\n\tmodules?: Module[];\n}\n\ntype TranslationCallExpression = {\n\ttype: 'comment' | 'call-expression';\n\tindex: number;\n\tvalue: string;\n};\n\ntype TranslationCallExpressions = Map<string, TranslationCallExpression[]>;\n\nconst MODULE_FILTERS = Object.freeze( [ /(([^!?\\s]+?)(?:\\.js|\\.jsx|\\.ts|\\.tsx))$/, /^((?!node_modules).)*$/ ] );\n\nconst COMMENTS_REGEXPS = Object.freeze( [\n\t// Matches translators comment block: `/* translators: %s */`.\n\t/\\/\\*[\\t ]*translators:.*\\*\\//gm,\n\t/// Matches translators inline comment: `// translators: %s`.\n\t/(\\/\\/)[\\t ]*translators:[^\\r\\n]*/gm,\n] );\n\nconst TRANSLATIONS_REGEXPS = Object.freeze( [\n\t// Matches translation functions: `__('Hello', 'elementor')`, `_n('Me', 'Us', 2, 'elementor-pro')`.\n\t/\\b_(?:_|n|nx|x)\\(.*?,\\s*(?<c>['\"`])[\\w-]+\\k<c>\\)/gm,\n] );\n\nexport class ExtractI18nWordpressExpressionsWebpackPlugin {\n\tapply( compiler: Compiler ) {\n\t\t// Learn more about Webpack plugin system: https://webpack.js.org/api/plugins/\n\n\t\tlet translationCallExpressions: TranslationCallExpressions = new Map();\n\n\t\t// Learn more about Webpack compilation process and hooks: https://webpack.js.org/api/compilation-hooks/\n\t\tcompiler.hooks.thisCompilation.tap( this.constructor.name, ( compilation ) => {\n\t\t\t// We tap into the time that Webpack has finished processing all the other assets\n\t\t\t// learn more: https://webpack.js.org/api/compilation-hooks/#processassets.\n\t\t\tcompilation.hooks.processAssets.tap( { name: this.constructor.name }, () => {\n\t\t\t\ttranslationCallExpressions = this.getModuleExpressionsMap( compilation );\n\t\t\t} );\n\t\t} );\n\n\t\tcompiler.hooks.afterEmit.tapPromise( this.constructor.name, async ( compilation ) => {\n\t\t\t// Create all the translations files based on the call expressions.\n\t\t\tawait this.createTranslationsFiles( compilation, translationCallExpressions );\n\t\t} );\n\t}\n\n\tgetModuleExpressionsMap( compilation: Compilation ) {\n\t\tconst moduleExpressionsMap = new Map();\n\n\t\t[ ...compilation.chunks ].forEach( ( chunk ) => {\n\t\t\tconst chunkJSFile = this.getFileFromChunk( chunk );\n\n\t\t\tif ( ! chunkJSFile ) {\n\t\t\t\t// There's no JS file in this chunk, no work for us. Typically a `style.css` from cache group.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcompilation.chunkGraph.getChunkModules( chunk ).forEach( ( module ) => {\n\t\t\t\tthis.getSubModulesToCheck( module ).forEach( ( subModule ) => {\n\t\t\t\t\tconst mainEntryFile = this.findMainModuleOfEntry( subModule, compilation );\n\n\t\t\t\t\tif ( ! moduleExpressionsMap.has( mainEntryFile ) ) {\n\t\t\t\t\t\tmoduleExpressionsMap.set( mainEntryFile, [] );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Running over the submodules and find all the translation call expressions and their translators comment\n\t\t\t\t\t// (e.g `/* translators: %s: name*/ __('Hello %s', 'elementor')`),\n\t\t\t\t\t// extract them and add them to a Map, where the key is the main entry file, and the value is an array of all the\n\t\t\t\t\t// translation call expressions.\n\t\t\t\t\tthis.extractExpressionsFromSubmodule( subModule ).forEach( ( expression ) => {\n\t\t\t\t\t\tmoduleExpressionsMap.get( mainEntryFile ).push( expression );\n\t\t\t\t\t} );\n\t\t\t\t} );\n\t\t\t} );\n\t\t} );\n\n\t\treturn moduleExpressionsMap;\n\t}\n\n\tasync createTranslationsFiles( compilation: Compilation, translationCallExpressions: TranslationCallExpressions ) {\n\t\tconst promises = [ ...compilation.entrypoints ].map( ( [ id, entrypoint ] ) => {\n\t\t\tconst chunk = entrypoint.chunks.find( ( { name } ) => name === id );\n\n\t\t\tif ( ! chunk ) {\n\t\t\t\treturn Promise.resolve();\n\t\t\t}\n\n\t\t\tconst chunkJSFile = this.getFileFromChunk( chunk );\n\n\t\t\tif ( ! chunkJSFile ) {\n\t\t\t\treturn Promise.resolve();\n\t\t\t}\n\n\t\t\tconst { entry } = compilation.options;\n\n\t\t\tif ( ! entry || typeof entry !== 'object' || ! ( id in entry ) ) {\n\t\t\t\tthrow new Error( `Entry must be an object. e.g: {app: './path/to/app.js'}` );\n\t\t\t}\n\n\t\t\tconst mainFilePath = entry[ id ].import?.[ 0 ];\n\n\t\t\tif ( ! mainFilePath ) {\n\t\t\t\tthrow new Error( 'Entry is invalid' );\n\t\t\t}\n\n\t\t\tconst { path: outputPath } = compilation.options.output;\n\n\t\t\tif ( ! outputPath ) {\n\t\t\t\tthrow new Error( 'Output path is invalid' );\n\t\t\t}\n\n\t\t\tconst assetFilename = this.generateTranslationFilename(\n\t\t\t\toutputPath,\n\t\t\t\tcompilation.getPath( '[file]', { filename: chunkJSFile } )\n\t\t\t);\n\n\t\t\tconst assetFileContent = this.generateTranslationFileContent(\n\t\t\t\ttranslationCallExpressions.get( mainFilePath ) || []\n\t\t\t);\n\n\t\t\treturn fs.promises.writeFile( assetFilename, assetFileContent );\n\t\t} );\n\n\t\treturn await Promise.all( promises );\n\t}\n\n\tgetSubModulesToCheck( module: Module ) {\n\t\treturn [ ...( module.modules || [] ), module ]\n\t\t\t.filter( ( subModule ) => this.shouldCheckModule( subModule ) );\n\t}\n\n\tgetFileFromChunk( chunk: Chunk ) {\n\t\treturn [ ...chunk.files ].find( ( f ) => /\\.js$/i.test( f ) );\n\t}\n\n\tshouldCheckModule( module: Module ) {\n\t\treturn MODULE_FILTERS.every( ( filter ) => filter.test( module.userRequest || '' ) );\n\t}\n\n\tfindMainModuleOfEntry( module: Module, compilation: Compilation ) : string | null {\n\t\tconst issuer = compilation.moduleGraph.getIssuer( module );\n\n\t\tif ( issuer ) {\n\t\t\treturn this.findMainModuleOfEntry( issuer, compilation );\n\t\t}\n\n\t\treturn module.rawRequest || null;\n\t}\n\n\textractExpressionsFromSubmodule( subModule: Module ) {\n\t\tconst source = subModule?._source?._valueAsString;\n\n\t\tif ( ! source ) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst translationCallExpressions: TranslationCallExpression[] = [];\n\n\t\t[\n\t\t\t...TRANSLATIONS_REGEXPS,\n\t\t\t...COMMENTS_REGEXPS,\n\t\t].forEach( ( regexp ) => {\n\t\t\t[ ...source.matchAll( regexp ) ].forEach( ( res ) => {\n\t\t\t\ttranslationCallExpressions.push( {\n\t\t\t\t\ttype: COMMENTS_REGEXPS.includes( regexp ) ? 'comment' : 'call-expression',\n\t\t\t\t\tindex: res.index || 0,\n\t\t\t\t\tvalue: res[ 0 ],\n\t\t\t\t} );\n\t\t\t} );\n\t\t} );\n\n\t\treturn translationCallExpressions;\n\t}\n\n\tgenerateTranslationFilename( basePath: string, filename: string ) {\n\t\treturn path.join(\n\t\t\tbasePath,\n\t\t\tfilename.replace( /(\\.min)?\\.js$/i, '.strings.js' )\n\t\t);\n\t}\n\n\tgenerateTranslationFileContent( expressions: TranslationCallExpression[] ) {\n\t\treturn expressions\n\t\t\t// Sort by the index it was found in the file based on the regexp (and not by the order it was added to the array).\n\t\t\t.sort( ( a, b ) => a.index - b.index )\n\t\t\t// Add a semicolon when needed.\n\t\t\t.map( ( expr ) => `${ expr.value }${ expr.type === 'comment' ? '' : ';' }` )\n\t\t\t// Join all the expressions to a single string with line-breaks between them.\n\t\t\t.join( '\\n' );\n\t}\n}\n"],"mappings":"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","names":["promises"]}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@elementor/extract-i18n-wordpress-expressions-webpack-plugin",
3
+ "version": "0.2.2",
4
+ "private": false,
5
+ "author": "Elementor Team",
6
+ "homepage": "https://elementor.com/",
7
+ "license": "GPL-3.0-or-later",
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.mjs",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/elementor/elementor-packages.git",
22
+ "directory": "packages/tools/extract-i18n-wordpress-expressions-webpack-plugin"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/elementor/elementor-packages/issues"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "build": "tsup --config=../../tsup.build.ts",
32
+ "dev": "tsup --config=../../tsup.dev.ts --format=esm,cjs"
33
+ },
34
+ "peerDependencies": {
35
+ "webpack": "5.x"
36
+ },
37
+ "gitHead": "336e3e6cbdd2cb43aa5f8ef9fa7982507f6f2c96"
38
+ }
@@ -0,0 +1,16 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`@elementor/extract-i18n-wordpress-expressions-webpack-plugin should extract translations from scripts 1`] = `
4
+ "__('basic','elementor');
5
+ __('basic with multi-word domain','elementor-pro-domain');
6
+ // translators: %s - regular comment.
7
+ __('regular comment %s','elementor');
8
+ /* translators: %s - comment block. */
9
+ __('comment block %s','elementor');
10
+ // translators: %1$s - special placeholder.
11
+ __('special placeholder %1$s','elementor');
12
+ __('inside console log', 'elementor');
13
+ _n('basic with','plural',2,'elementor');
14
+ _nx('another with','plural',2,'elementor');
15
+ _x('context','elementor');"
16
+ `;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { webpack } from 'webpack';
8
+ import { ExtractI18nWordpressExpressionsWebpackPlugin } from '../index';
9
+ import { vol } from 'memfs';
10
+
11
+ jest.mock( 'fs', () => jest.requireActual( 'memfs' ) );
12
+
13
+ describe( '@elementor/extract-i18n-wordpress-expressions-webpack-plugin', () => {
14
+ beforeEach( () => {
15
+ const fileContent = `
16
+ export default function Component() {
17
+ const basic = __('basic','elementor');
18
+
19
+ const a = 'Some code in the middle';
20
+
21
+ const basic2 = __('basic with multi-word domain','elementor-pro-domain');
22
+
23
+ // translators: %s - regular comment.
24
+ const withComment = __('regular comment %s','elementor');
25
+
26
+ /* translators: %s - comment block. */
27
+ const withCommentBlock = __('comment block %s','elementor');
28
+
29
+ // translators: %1$s - special placeholder.
30
+ const withSpecialPlaceHolder = __('special placeholder %1$s','elementor');
31
+
32
+ console.log(__('inside console log', 'elementor'))
33
+
34
+ return [
35
+ _n('basic with','plural',2,'elementor'),
36
+ _nx('another with','plural',2,'elementor'),
37
+ _x('context','elementor'),
38
+ invalid__('invalid','elementor'),
39
+ ]
40
+ }`;
41
+
42
+ fs.writeFileSync( path.resolve( '/app.js' ), fileContent );
43
+ } );
44
+
45
+ afterEach( () => {
46
+ vol.reset();
47
+ } );
48
+
49
+ it( 'should extract translations from scripts', ( done ) => {
50
+ // Arrange.
51
+ const compiler = webpack( {
52
+ mode: 'development',
53
+ entry: {
54
+ app: path.resolve( '/app.js' ),
55
+ },
56
+ output: {
57
+ filename: '[name].js',
58
+ path: path.resolve( '/dist' ),
59
+ },
60
+ plugins: [
61
+ new ExtractI18nWordpressExpressionsWebpackPlugin(),
62
+ ],
63
+ } );
64
+
65
+ // Expect.
66
+ expect.assertions( 4 );
67
+
68
+ // Act.
69
+ compiler.run( ( err, stats ) => {
70
+ // Assert.
71
+ expect( err ).toBe( null );
72
+ expect( stats?.hasErrors() ).toBe( false );
73
+ expect( stats?.hasWarnings() ).toBe( false );
74
+
75
+ const fileContent = fs.readFileSync( path.resolve( `/dist/app.strings.js` ), { encoding: 'utf8' } );
76
+
77
+ expect( fileContent ).toMatchSnapshot();
78
+
79
+ done();
80
+ } );
81
+ } );
82
+ } );