Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2873749
wp-build-polyfills: add stamp-textdomains post-build tool
dhasilva Jul 22, 2026
a6c1ba2
wp-build-polyfills: add shared loadI18nCatalogs helper
dhasilva Jul 22, 2026
834b436
activity-log: use shared i18n helper + stamp text domains at build
dhasilva Jul 22, 2026
f173ad7
publicize: load JS translation catalogs + stamp text domains
dhasilva Jul 22, 2026
f75428c
videopress: load JS translation catalogs + stamp text domains
dhasilva Jul 22, 2026
d6b8553
backup: load JS translation catalogs + stamp text domains
dhasilva Jul 22, 2026
41fb5a0
newsletter: load JS translation catalogs + stamp text domains
dhasilva Jul 22, 2026
21b7022
forms: load JS translation catalogs + stamp text domains
dhasilva Jul 22, 2026
7943797
premium-analytics: load JS translation catalogs + stamp text domains
dhasilva Jul 22, 2026
a196c2f
changelog
dhasilva Jul 22, 2026
34785f4
wp-build-polyfills: replace stripped bundles with i18n stubs
dhasilva Jul 22, 2026
cd9df6d
wp-build-polyfills: cover the CLI wrappers and the i18n catalog helper
dhasilva Jul 23, 2026
a873ca9
premium-analytics: set beta-plugin-slug so the i18n map generates, an…
enejb Jul 24, 2026
1982e7b
wp-build-polyfills: derive catalog lists from a build i18n manifest a…
enejb Jul 24, 2026
6ebbd00
wp-build-polyfills: defer widget catalogs, revalidate the manifest, a…
enejb Jul 24, 2026
af6e152
wp-build: run boot init modules in the page bootstrap since core's ol…
enejb Jul 25, 2026
24bcccd
assets, composer-plugin: never emit or honor a textdomain self-alias …
enejb Jul 25, 2026
463e101
assets, composer-plugin: keep the package path when a textdomain can'…
dhasilva Jul 27, 2026
e304546
babel-plugin-replace-textdomain: don't rewrite calls proven to come f…
dhasilva Jul 27, 2026
12248e3
wp-build-polyfills: rehash the asset file of every bundle the stamp r…
dhasilva Jul 27, 2026
5c1742a
wp-build-polyfills: only stay silent on a 404 catalog response
dhasilva Jul 27, 2026
19847a7
premium-analytics: clean before building; drop lockfile tarball churn
dhasilva Jul 27, 2026
6096e4b
assets: name the one-package-per-domain constraint on $domain_paths
dhasilva Jul 27, 2026
a7a40ae
premium-analytics: load catalogs under the renamed package text domain
dhasilva Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .pnpm-patches/@wordpress__build.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
diff --git a/templates/page-wp-admin.php.template b/templates/page-wp-admin.php.template
index 96c4627..48fcf28 100644
--- a/templates/page-wp-admin.php.template
+++ b/templates/page-wp-admin.php.template
@@ -171,7 +171,26 @@ function {{PREFIX}}_{{PAGE_SLUG_UNDERSCORE}}_wp_admin_enqueue_scripts( $hook_suf
( mountId, routes, initModules ) => {
const run = async () => {
const mod = await import( "@wordpress/boot" );
- mod.initSinglePage( { mountId, routes, initModules } );
+ /*
+ * Run the init modules here instead of delegating to
+ * initSinglePage(): WordPress cores that bundle an older
+ * @wordpress/boot (initModules support postdates WP 7.0's copy,
+ * and the import map resolves @wordpress/boot to core's bundle
+ * when core provides one) silently ignore the option, so init
+ * modules would never execute. Running them before
+ * initSinglePage() gives identical behavior on every core.
+ */
+ for ( const id of initModules ?? [] ) {
+ try {
+ const initModule = await import( id );
+ if ( typeof initModule.init === "function" ) {
+ await initModule.init();
+ }
+ } catch ( error ) {
+ console.warn( "Failed to run boot init module:", id, error );
+ }
+ }
+ mod.initSinglePage( { mountId, routes } );
};
if ( document.readyState === "loading" ) {
document.addEventListener( "DOMContentLoaded", run );
63 changes: 41 additions & 22 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,10 @@ patchedDependencies:
# uuid up to v11 had CommonJS files with ESM content too.
# No bug report, because it's already fixed in the latest versions.
"uuid@<11": .pnpm-patches/uuid.patch

# The wp-admin page template delegates boot init modules to initSinglePage(),
# but WordPress cores bundling an older @wordpress/boot (e.g. WP 7.0) ignore
# the option, so init modules never run there. The patch runs them in the
# inline bootstrap instead. Remove once the template runs init modules itself
# upstream (or Jetpack's WP floor ships a boot with initModules support).
"@wordpress/build": .pnpm-patches/@wordpress__build.patch
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Plugin options are:
The default function list handles the `__`, `_x`, `_n`, and `_nx` functions provided by [@wordpress/i18n]. This list may be accessed as `require( '@automattic/babel-plugin-replace-textdomain' ).defaultFunctions`.

- `i18nModule`: Specify the module name used for resolving import aliases. When a function is imported from this module and called under an alias, the plugin will still recognize it. Defaults to `@wordpress/i18n`.
- `requireI18nSource`: Skip calls whose callee provably resolves to something other than `i18nModule`. Defaults to false.

By default any call matching the function list is rewritten, including member calls like `cache.__( key )`. That is fine for hand-written source, but when the plugin runs over a whole bundle — where a dependency's own helpers sit in the same file as the real gettext calls — it rewrites calls that have nothing to do with i18n. With this option a callee is left alone once it can be traced to another module, while a callee whose origin can't be determined is still rewritten (so an unrecognized bundler shape doesn't silently leave a bundle untranslated).

To report instances of the specified i18n functions called without a domain or with an improper value for the domain, set the `DEBUG` environment variable to include `@automattic/babel-plugin-replace-textdomain`.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add a `requireI18nSource` option to skip gettext-shaped calls whose callee provably comes from a module other than the i18n one — needed when the plugin runs over whole bundles rather than hand-written source.
49 changes: 11 additions & 38 deletions projects/js-packages/babel-plugin-replace-textdomain/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const pluginName = require( '../package.json' ).name;
const debug = require( 'debug' )( pluginName ); // eslint-disable-line import/order
const { resolveGettextCallee } = require( './resolve-gettext-callee.js' );

const defaultFunctions = Object.freeze( {
__: 1,
Expand All @@ -10,36 +11,14 @@ const defaultFunctions = Object.freeze( {

const defaultI18nModule = '@wordpress/i18n';

/**
* Checks whether a given function name is an alias for an i18n module import.
* @param {object} path - The Babel path object.
* @param {string} name - The function name to check.
* @param {string} i18nModule - The module name to match against.
* @return {string|null} The resolved function name, or null if the function name is not an alias.
*/
function resolveI18nAlias( path, name, i18nModule ) {
const binding = path.scope.getBinding( name );
if ( ! binding ) {
return null;
}
const bindingPath = binding.path;
if ( ! bindingPath.isImportSpecifier() ) {
return null;
}
const importDecl = bindingPath.parentPath;
if ( ! importDecl.isImportDeclaration() || importDecl.node.source.value !== i18nModule ) {
return null;
}
return bindingPath.node.imported.name;
}

module.exports = ( babel, opts ) => {
const { types: t } = babel;
const seenDomains = {};

let functions = defaultFunctions;
let i18nModule = defaultI18nModule;
let replacementDomain;
const requireI18nSource = !! opts.requireI18nSource;

if ( typeof opts.textdomain === 'undefined' ) {
throw new Error( `${ pluginName }: The \`textdomain\` option is not set.` );
Expand Down Expand Up @@ -78,21 +57,13 @@ module.exports = ( babel, opts ) => {
name: pluginName,
visitor: {
CallExpression( path ) {
let callee = path.node.callee;
if ( t.isSequenceExpression( callee ) ) {
callee = callee.expressions[ callee.expressions.length - 1 ];
}
const calleeName = t.isMemberExpression( callee ) ? callee.property.name : callee.name;

let funcName = calleeName;
if ( ! Object.hasOwn( functions, funcName ) ) {
if ( t.isMemberExpression( callee ) ) {
return;
}
funcName = resolveI18nAlias( path, calleeName, i18nModule );
if ( ! funcName || ! Object.hasOwn( functions, funcName ) ) {
return;
}
const funcName = resolveGettextCallee( t, path, {
functions,
i18nModule,
requireI18nSource,
} );
if ( ! funcName ) {
return;
}
const idx = functions[ funcName ];

Expand Down Expand Up @@ -149,3 +120,5 @@ module.exports = ( babel, opts ) => {
};

module.exports.defaultFunctions = defaultFunctions;
module.exports.defaultI18nModule = defaultI18nModule;
module.exports.resolveGettextCallee = resolveGettextCallee;
Loading
Loading