<div style="text-align:center"></div><div>
<p>A recent <a href="https://make.wordpress.org/core/2023/05/25/wordpress-6-2-server-performance-analysis-summary/">in-depth performance analysis of WordPress core</a> showed that loading translations had a significant hit on a site’s server response time. Given that <a href="https://wordpress.org/about/stats/">more than half of all WordPress sites</a> use a language other than English, the performance team identified this as an area worth looking into more closely. The team spent the last couple of months exploring this in more detail and the results are now shared in this blog post.</p>
<div class="callout callout-info">
<p><em>This is merely an analysis of the current i18n system in WordPress with some proposed under-the-hood performance improvements. No decisions have been made on any of these proposals. </em></p>
</div>
<h2 class="wp-block-heading"><strong>Context</strong></h2>
<p>Initial benchmarks showed that the median loading time for a localized site can be <strong>up to 50% slower</strong> than for non-localized sites, depending on which themes and plugins are being used. This was measured using both the <a href="https://github.com/GoogleChromeLabs/wpp-research/">wpp-research CLI tool</a> and also a dedicated benchmark environment (as elaborated in the Comparison section towards the end).</p>
<p>The WordPress i18n system is based on <a href="https://www.gnu.org/software/gettext/">gettext</a>, which uses source <code>.po</code> (Portable Object) files and binary <code>.mo</code> (Machine Object) files for storing and loading translations. It is not using the C gettext API itself but a custom userland implementation that works without any external dependencies.</p>
<p>In addition to core itself, each plugin and theme has its own translation file, which has to be loaded and parsed on every request. Loading and parsing all these translation files is an expensive task.</p>
<p>In the past, various solutions have been discussed and explored to improve the i18n performance of WordPress. A non-exhaustive list:</p>
<ul>
<li>Use a more lightweight MO parser</li>
<li>Improve translation lookups by using the hash map in MO files (e.g. with <a href="https://wordpress.org/plugins/dynamo/">DynaMo</a>)</li>
<li>Caching translations in the object cache</li>
<li>Caching translations in APCu (an in-memory key-value store for PHP)</li>
<li>Other more elaborated forms of caching (e.g. per request)</li>
<li>Using the native PHP gettext extension</li>
<li>Use a custom PHP extension to handle the MO file parsing)</li>
<li>Using lazily evaluated translation calls (see <a href="https://core.trac.wordpress.org/ticket/41305">#41305</a> for details)</li>
<li>Using a different file format than <code>.mo</code> files, e.g. plain <code>.php</code> files</li>
</ul>
<p>A more recent discussion touching on all of these solutions can be found <a href="https://github.com/WordPress/performance/issues/171">over at the wordpress/performance repository</a>. It’s a great way to get some context on this topic.</p>
<p>For this analysis, many of these solutions were looked at, focusing on their advantages and disadvantages. At the end of this post there is a comparison table with some much needed numbers as well, based on custom-built benchmarks.</p>
<h2 class="wp-block-heading"><strong>Solutions</strong></h2>
<h3 class="wp-block-heading"><strong>Solution A: Use different file format</strong></h3>
<p>Use a different file format for translations instead of .mo files to avoid the overhead of loading and parsing binary files.</p>
<h4 class="wp-block-heading"><strong>Design considerations</strong></h4>
<p>With this solution, translations will be stored in plain <code>.php</code> files returning an associative array of translation strings. Whenever a <code>.php</code> file is available, it will be preferred over the <code>.mo</code> file, which is still used as a fallback. The rest of the architecture remains the same.</p>
<p>When a localized WordPress site downloads language packs from the <a href="http://translate.wordpress.org/">translate.wordpress.org</a> translation platform, it downloads <code>.po</code> and <code>.mo</code> files containing all the translations. This will be modified to include <code>.php</code> files. <a href="https://glotpress.blog/">GlotPress</a>, which the platform is built on, will be updated to support this new output format. Additionally, WordPress core itself could be modified to generate PHP files whenever they are missing.</p>
<p>In theory, nothing is faster in PHP than loading and executing another PHP file. <code>.json</code>, <code>.ini</code>, or <code>.xml</code> would all be much slower.</p>
<p>Proof of concepts using the PHP files can be found at <a href="https://github.com/swissspidy/wp-php-translation-files">swissspidy/wp-php-translation-files</a> and <a href="https://github.com/swissspidy/ginger-mo/">swissspidy/ginger-mo</a>.</p>
<h4 class="wp-block-heading"><strong>Benefits</strong></h4>
<ul>
<li>Initial benchmarks show consistent significant performance improvements</li>
<li>Relatively trivial to implement</li>
<li>Maintains backward compatibility thanks to graceful fallback</li>
<li>Makes it easier for users to inspect and change translations (no need to compile <code>.po</code> to <code>.mo</code>)</li>
<li>Avoids loading and parsing binary <code>.mo</code> files, which is the main bottleneck</li>
<li>Lets PHP store translations in OPcache for an additional performance benefit</li>
<li>Battle-tested approach in the PHP ecosystem (for example in Laravel)</li>
</ul>
<h4 class="wp-block-heading"><strong>Caveats and risks</strong></h4>
<ul>
<li>Requires not only changes to WordPress core, but also tools like GlotPress and WP-CLI</li>
<li>Adds maintenance overhead by introducing a new file format on top of the existing one
<ul>
<li>As shown by the proof of concept, the overhead is minimal</li>
<li>In the long term, <code>.mo</code> support could be deprecated</li>
</ul>
</li>
<li>Security considerations due to essentially executing remotely fetched PHP files
<ul>
<li>Not really different from downloading plugins/themes from <a href="http://wordpress.org/">WordPress.org</a></li>
<li>WordPress considers translations to be trusted</li>
<li>Hosting providers could be blocking PHP execution in wp-content/languages</li>
<li>Could potentially use checksum verifications or static analysis at install time to detect anomalies</li>
</ul>
</li>
</ul>
<h4 class="wp-block-heading"><strong>Effort and timeline</strong></h4>
<p>The proof of concept using PHP files is in a very solid state already. There are also examples for changes to WP-CLI (<a href="https://github.com/wp-cli/i18n-command/pull/363">PR</a>) and GlotPress (<a href="https://github.com/GlotPress/GlotPress/pull/1626">PR</a>). This makes it suitable for a feature project to expand testing with very little effort required. Even a core merge would be very straightforward in a relatively short time, potentially already in Q4 2023. The security aspect when using PHP files could be a potential blocker, so it’s important to loop in the WordPress security team and hosting providers early on.</p>
<p>More time is required to test other file formats and compare results.</p>
<h3 class="wp-block-heading"><strong>Solution B: Native gettext extension</strong></h3>
<p>Use the <a href="https://www.php.net/manual/en/book.gettext.php">native gettext PHP extension</a> written in C when available, instead of the custom built-in parser in WordPress.</p>
<h4 class="wp-block-heading"><strong>Design considerations</strong></h4>
<p>WordPress has always used a custom MO file parser, because the native gettext extension is not necessarily available on the server. With this solution, the existing system is adapted to use the extension whenever available and falling back to the custom implementation if not.</p>
<p>This has been previously explored in <a href="https://core.trac.wordpress.org/ticket/17268">#17268</a> and implemented in <a href="https://wordpress.org/plugins/wp-performance-pack/">WP Performance Pack</a> and <a href="https://wordpress.org/plugins/native-gettext/">Native Gettext</a>. These implementations can serve as inspiration for the initial design. They all work similarly in that they symlink or copy the translation files to a new directory structure that is compatible with the gettext extension.</p>
<p>As of July 2023, around 66% of all localized WordPress sites have the gettext extension installed, according to information from the WordPress update requests.</p>
<h4 class="wp-block-heading"><strong>Benefits</strong></h4>
<ul>
<li>Significant performance improvements for eligible sites
<ul>
<li>Initial benchmarks show that loading time and memory usage basically do not differ from non-localized sites</li>
</ul>
</li>
</ul>
<h4 class="wp-block-heading"><strong>Caveats and risks</strong></h4>
<ul>
<li>The gettext extension is not commonly available
<ul>
<li>Smaller incentive to implement and lower impact overall</li>
</ul>
</li>
<li>Requires locales to be installed on the server
<ul>
<li>Servers rarely have many installed locales
<ul>
<li>Locales often need to be compiled first and take up a lot of space</li>
<li>WordPress on the other hand supports over 200 locales</li>
</ul>
</li>
<li>Potential clashes with the custom locales WordPress supports
<ul>
<li>For example, locales like <code>pt_PT_ao90</code>, <code>de_DE_formal</code> or <code>roh</code> might not even be supported</li>
</ul>
</li>
<li>Outreach to hosting providers would be necessary</li>
</ul>
</li>
<li>Adds maintenance overhead by essentially adding a second gettext implementation</li>
<li>Poor API
<ul>
<li>Requires setting environment variables (such as <code>LC_MESSAGES</code> and <code>LANGUAGE</code>), which might not be possible or cause conflicts on certain servers/sites</li>
</ul>
</li>
<li>Requires symlinks or hard file copies
<ul>
<li>Symlinks might not be possible on the server; copying all translation files means doubling disk usage</li>
</ul>
</li>
<li>Translation files are cached by PHP, thus any translation change requires restarting the web server
<ul>
<li>There are <a href="https://stackoverflow.com/a/13629035/1185292">workarounds</a> such as cache busting using random file names or fstat, however they might not work on all environments</li>
</ul>
</li>
<li>Has not been tested on a wider scale, despite being discussed for years</li>
</ul>
<p>Check out the code of <a href="https://wordpress.org/plugins/wp-performance-pack/">WP Performance Pack</a> and <a href="https://wordpress.org/plugins/native-gettext/">Native Gettext</a> to get a better idea of the extension’s poor API.</p>
<h4 class="wp-block-heading"><strong>Effort and timeline</strong></h4>
<p>While there are existing implementations that could be leveraged for this solution, further field testing is required to assess whether the extension actually works under all circumstances. Given the limitations around the poor API and requirements for installing locales, it does not seem like a viable solution at all.</p>
<h3 class="wp-block-heading"><strong>Solution C: Cache translations</strong></h3>
<p>Cache translations somehow to avoid expensive <code>.mo</code> parsing.</p>
<h4 class="wp-block-heading"><strong>Design considerations</strong></h4>
<p>Cache translations either on disk, in the database, or the object cache to avoid expensive <code>.mo</code> file parsing on subsequent requests. This can be done in a generalized manner or also on a per-request basis to only load translations required for the current URL.</p>
<p>Many different caching strategies have been explored in various forms in the past, each with their own pros and cons. Some could even be combined. Defining the exact implementation requires further exploration and testing, which warrants its own exploration post.</p>
<h4 class="wp-block-heading"><strong>Benefits</strong></h4>
<ul>
<li>Caching translations after one time <code>.mo</code> parsing potentially improves performance for future requests</li>
</ul>
<h4 class="wp-block-heading"><strong>Caveats and risks</strong></h4>
<ul>
<li>Caching using persistent object cache (e.g. Memcached, Redis) or APCu:
<ul>
<li>Not available on most sites, making this not an ideal solution
<ul>
<li>Availability according to data from WordPress update requests:
<ul>
<li>Memcached: ~25%</li>
<li>Redis: ~25%</li>
<li>APCu: ~6%</li>
</ul>
</li>
</ul>
</li>
<li>Can potentially significantly increase cache size or exceed cache key limits</li>
</ul>
</li>
<li>Database caching:
<ul>
<li>Moves the problem from disk reads to database reads</li>
<li>Can potentially significantly increase database size</li>
<li>Alternatively, use sqlite as a cache backend
<ul>
<li>Untested approach</li>
<li>Available on around 90% of sites</li>
</ul>
</li>
</ul>
</li>
<li>Disk caching:
<ul>
<li>Not always possible, depending on server environment</li>
<li>Still causes file reads, only with fewer or other files</li>
</ul>
</li>
<li>Multiple cache groups (e.g. per-request or frontend/admin split)
<ul>
<li>Smarter cache logic to only load translations that are needed for the majority of requests</li>
<li>Can potentially significantly increase cache size</li>
<li>Unlikely that different requests use very different translations</li>
</ul>
</li>
<li>Cache retrieval adds overhead
<ul>
<li>Exact performance gains depend on implementation method and need to be measured first</li>
<li>No performance gains with cold cache</li>
<li>Cache invalidation logic TBD</li>
</ul>
</li>
</ul>
<h4 class="wp-block-heading"><strong>Effort and timeline</strong></h4>
<p>Given the existing solutions in the ecosystem, the engineering effort itself would not be too big, but the right caching implementation (e.g. disk cache or object cache) needs to be evaluated first.</p>
<p>However, the right caching strategy probably does not exist because of all the different hosting environments. Since it’s unrealistic for core to support multiple types of caching, this solution seems better suited for plugins rather than core.</p>
<h3 class="wp-block-heading"><strong>Solution D: Lazily evaluated translation calls</strong></h3>
<p>Use lazily evaluated translation calls to reduce the number of function calls in certain cases, leading to improved performance.</p>
<h4 class="wp-block-heading"><strong>Design considerations</strong></h4>
<p>The idea of lazily evaluated translation calls has been first discussed in <a href="https://core.trac.wordpress.org/ticket/41305">#41305</a>. It enables avoiding string-specific expensive translation lookups until the translations are actually needed, by passing around proxy objects.</p>
<p>In other words: beyond just-in-time loading of translation files (which WordPress already does), this would add just-in-time lookup of individual strings in the translations. <a href="https://github.com/WordPress/wordpress-develop/pull/4444">Check out this proof of concept</a> to get a better picture.</p>
<p>It can be integrated essentially in two ways, both of which are explained on the core ticket:</p>
<ol>
<li>Change all translation calls to be lazily evaluated by default</li>
<li>Make this opt-in, either with new function arguments or new functions altogether</li>
</ol>
<h4 class="wp-block-heading"><strong>Benefits</strong></h4>
<ul>
<li>Reduces the number of translation lookups, in some scenarios drastically
<ul>
<li>On a regular home page request there are ~60% less translation calls, saving around ~10ms (as <a href="https://make.wordpress.org/performance/handbook/measuring-performance/profiling-php-performance-with-xhprof/">measured by XHProf</a>)</li>
</ul>
</li>
<li>As a side effect, solves UX issues such as <a href="https://core.trac.wordpress.org/ticket/38643">#38643</a></li>
</ul>
<h4 class="wp-block-heading"><strong>Caveats and risks</strong></h4>
<ul>
<li>Depending on implementation this either breaks backward compatibility or risks not gaining enough adoption
<ul>
<li>Documentation, tooling, and developer education can help mitigate this to a certain extent</li>
<li>Adoption could be done gradually, e.g. starting with an opt-in approach and eventually making it the default</li>
</ul>
</li>
<li>Likely will not have a significant impact on typical frontend page loads, as it’s mostly useful for areas like the REST API schema output, where a lot of translation calls are made without actually using the translations
<ul>
<li>Needs analysis in more scenarios to measure impact</li>
<li>The REST API schema already has a workaround by using a cache in a static variable</li>
</ul>
</li>
<li>Does not improve situation for actually loading translation files</li>
<li>Initial testing shows that this actually hurts performance due to the additional thousands of proxy objects being created</li>
</ul>
<h4 class="wp-block-heading"><strong>Effort and timeline</strong></h4>
<p>Gradual adoption would mean a multi-year effort to establish lazily evaluated translation calls, while enabling this by default is a significant backward compatibility break that could affect thousands of plugins and themes in the ecosystem. And since it does actually slow down performance in some cases, this solution is not a great candidate for implementation.</p>
<h3 class="wp-block-heading"><strong>Solution E: Optimize/Rewrite existing MO parser</strong></h3>
<p>Refactor the existing MO parser in WordPress to be more performant.</p>
<h4 class="wp-block-heading"><strong>Design considerations</strong></h4>
<p>Completely overhaul the existing MO translation file parser in WordPress with performance in mind. For example by using <a href="https://github.com/dd32/ginger-mo">Ginger MO</a>, <a href="https://wordpress.org/plugins/wp-performance-pack/">WP Performance Pack</a>, or other existing solutions as a base.</p>
<p>While for instance <a href="https://www.altis-dxp.com/">Altis DXP</a> (Human Made) have actually replaced the existing MO parser with a custom-made PHP extension written in Rust, such an approach is obviously not feasible for core. The new solution needs to be written in userland PHP.</p>
<p>Initial testings with an <a href="https://github.com/swissspidy/ginger-mo">updated fork of Ginger MO</a> show some noticeable speedups and lower memory usage. It also supports multiple translation files per text domain and multiple locales loaded at once, which could prove beneficial for improving the locale switching functionality in WordPress core.</p>
<p>Besides that, plugins like <a href="https://wordpress.org/plugins/wp-performance-pack/">WP Performance Pack</a> and <a href="https://wordpress.org/plugins/dynamo/">DynaMo</a> have implemented partial lookups using <a href="https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html">the MO hash table</a> or binary search, avoiding reading the whole file and storing it in memory. That slightly reduces memory usage and performance.</p>
<h4 class="wp-block-heading"><strong>Benefits</strong></h4>
<ul>
<li>Can be used without necessarily introducing another file format</li>
<li>Opens up potential performance enhancements in other areas, i.e. locale switching</li>
<li>Mostly maintains backward compatibility</li>
</ul>
<h4 class="wp-block-heading"><strong>Caveats and risks</strong></h4>
<ul>
<li>Still a risk of breaking backward compatibility</li>
</ul>
<h4 class="wp-block-heading"><strong>Effort and timeline</strong></h4>
<p>There already is a working proof of concept for this solution, but more testing is required to further refine it and improve its backward compatibility layer. With such an effort being an ideal candidate for a feature plugin, this could be achieved relatively quickly in a few months.</p>
<h3 class="wp-block-heading"><strong>Solution F: Splitting up translation files</strong></h3>
<p>Split translation files from plugins and themes into smaller chunks to make loading them more efficient.</p>
<h4 class="wp-block-heading"><strong>Design considerations</strong></h4>
<p>Depending on the project’s size, translation files can be quite big. That’s why WordPress itself uses separate translation files for the admin and everything else, so that not too many strings are unnecessarily loaded.</p>
<p>This strategy could be applied to plugins and themes as well. Either by allowing them to use multiple text domains (which would require developer education and changes to tooling), or by somehow doing this automatically (exact method TBD)</p>
<h4 class="wp-block-heading"><strong>Benefits</strong></h4>
<ul>
<li>Faster loading times due to loading smaller files</li>
</ul>
<h4 class="wp-block-heading"><strong>Caveats and risks</strong></h4>
<ul>
<li>Risk of breaking backward compatibility</li>
<li>Opt-in approach requires tooling and distribution changes and risks slow adoption</li>
</ul>
<h4 class="wp-block-heading"><strong>Effort and timeline</strong></h4>
<p>Further research is required to evaluate this properly.</p>
<h2 class="wp-block-heading"><strong>Comparison</strong></h2>
<p>At first glance,<strong> solution A</strong> (PHP translation files) is a relatively straightforward enhancement that maintains backward compatibility and shows promising improvements. However, it does not only require changes to core itself, but also to the translation platform. The security aspect remains a risk, although discussing it early on with stakeholders and gathering more testers would help mitigate it.</p>
<p>Leveraging the native gettext extension as in <strong>solution B</strong> shows stunning results, but the lack of availability and the non-ideal API are a concern. Still, it’s a progressive enhancement that cannot be ignored. Especially since it could pretty much eliminate the need for additional caching as in solution C.</p>
<p>Caching already loaded translations as in <strong>solution C</strong> does not eliminate the root cause of the i18n slowness, but can speed up subsequent requests. Unfortunately, persistent object caches or APCu are rather uncommon (though we do not have exact data on the former yet, see <a href="https://core.trac.wordpress.org/ticket/58808">#58808</a>), and implementing more complex types of caching (e.g. per-request caching) would require significant exploration effort before becoming a viable option.</p>
<p>Lazily evaluated translation calls (<strong>solution D</strong>) can shave time off translation calls in some situations, but overall actually decrease performance. While it could help solve some actual UX issues in core, the backward compatibility and adoption concerns make it even less of a suitable solution.</p>
<p>Existing plugins like Ginger MO and WP Performance Pack show that the existing MO parser in WordPress can be further improved (<strong>solution E</strong>).</p>
<h3 class="wp-block-heading"><strong>Benchmarks</strong></h3>
<p>Now to the most interesting part: the hard numbers!</p>
<p>These benchmarks are powered by a custom-built performance testing environment using <code><a href="https://npmjs.com/package/@wordpress/env">@wordpress/env</a></code> and <a href="https://playwright.dev/">Playwright</a>. The environment has been configured with some additional plugins and the PHP extensions required for some of the solutions. Tests have been performed against the 6.3 RC by visiting the home page and the dashboard 30 times each and then using the median values.</p>
<p>You can find the exact setup in this <a href="https://github.com/swissspidy/wp-i18n-benchmarks">wp-i18n-benchmarks GitHub repository</a>.</p>
<h4 class="wp-block-heading"><strong>Block Theme</strong></h4>
<figure class="wp-block-table is-style-stripes">
<table class="has-fixed-layout">
<thead>
<tr>
<th><strong>Locale</strong></th>
<th><strong>Scenario</strong></th>
<th><strong>Object Cache</strong></th>
<th><strong>Memory Usage</strong></th>
<th><strong>Total Load Time</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>en_US</td>
<td>Default</td>
<td></td>
<td>15.60 MB</td>
<td>133.58 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Default</td>
<td></td>
<td>29.14 MB</td>
<td>181.95 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (MO)</td>
<td></td>
<td>19.24 MB</td>
<td>159.18 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (PHP)</td>
<td></td>
<td>16.98 MB</td>
<td>138.14 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (JSON)</td>
<td></td>
<td>19.24 MB</td>
<td>153.39 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Native Gettext</td>
<td></td>
<td>15.99 MB</td>
<td>142.12 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>DynaMo</td>
<td></td>
<td>19.62 MB</td>
<td>157.93 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Cache in APCu</td>
<td></td>
<td>50.37 MB</td>
<td>181.51 ms</td>
</tr>
<tr>
<td>en_US</td>
<td>Default</td>
<td></td>
<td>29.01 MB</td>
<td>167.67 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (MO)</td>
<td></strong></td>
<td>16.85 MB</td>
<td>127.97 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (JSON)</td>
<td></td>
<td>15.86 MB</td>
<td>129.19 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>DynaMo</td>
<td></td>
<td>50.30 MB</td>
<td>170.19 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Cache in object cache</td>
<td><imgsrc="" alt="â " class="wp-smiley" style="height: 1em; max-height: 1em;"></td>
<td>29.07 MB</td>
<td>173.19 ms</td>
</tr>
</tbody>
</table><figcaption class="wp-element-caption">Benchmarks using the Twenty Twenty-Three block theme</figcaption></figure>
<h4 class="wp-block-heading"><strong>Classic Theme</strong></h4>
<figure class="wp-block-table is-style-stripes">
<table class="has-fixed-layout">
<thead>
<tr>
<th><strong>Locale</strong></th>
<th><strong>Scenario</strong></th>
<th><strong>Object Cache</strong></th>
<th><strong>Memory Usage</strong></th>
<th><strong>Total Load Time</strong></th>
</tr>
<tr>
<th>en_US</th>
<th>Default</th>
<th></th>
<th>15.35 MB</th>
<th>120.79 ms</th>
</tr>
</thead>
<tbody>
<tr>
<td>de_DE</td>
<td>Default</td>
<td></td>
<td>28.79 MB</td>
<td>172.10 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (MO)</td>
<td></td>
<td>18.85 MB</td>
<td>145.68 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (PHP)</td>
<td></td>
<td>16.56 MB</td>
<td>124.73 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (JSON)</td>
<td></td>
<td>18.84 MB</td>
<td>140.78 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Native Gettext</td>
<td></td>
<td>15.58 MB</td>
<td>128.26 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>DynaMo</td>
<td></td>
<td>19.24 MB</td>
<td>146.09 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Cache in APCu</td>
<td></td>
<td>50.13 MB</td>
<td>167.28 ms</td>
</tr>
<tr>
<td>en_US</td>
<td>Default</td>
<td></td>
<td>28.59 MB</td>
<td>154.30 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (MO)</td>
<td></strong></td>
<td>16.37 MB</td>
<td>112.94 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (JSON)</td>
<td></td>
<td>15.38 MB</td>
<td>115.11 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>DynaMo</td>
<td></td>
<td>49.99 MB</td>
<td>151.82 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Cache in object cache</td>
<td><imgsrc="" alt="â " class="wp-smiley" style="height: 1em; max-height: 1em;"></td>
<td>28.65 MB</td>
<td>156.36 ms</td>
</tr>
</tbody>
</table><figcaption class="wp-element-caption">Benchmarks using the Twenty Twenty-One classic theme</figcaption></figure>
<h4 class="wp-block-heading"><strong>Admin</strong></h4>
<figure class="wp-block-table is-style-stripes">
<table class="has-fixed-layout">
<thead>
<tr>
<th><strong>Locale</strong></th>
<th><strong>Scenario</strong></th>
<th><strong>Object Cache</strong></th>
<th><strong>Memory Usage</strong></th>
<th><strong>Total Load Time</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>en_US</td>
<td>Default</td>
<td></td>
<td>15.42 MB</td>
<td>139.83 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Default</td>
<td></td>
<td>31.92 MB</td>
<td>187.76 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (MO)</td>
<td></td>
<td>20.07 MB</td>
<td>164.94 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (PHP)</td>
<td></td>
<td>17.09 MB</td>
<td>139.66 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (JSON)</td>
<td></td>
<td>20.06 MB</td>
<td>160.87 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Native Gettext</td>
<td></td>
<td>15.95 MB</td>
<td>143.43 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>DynaMo</td>
<td></td>
<td>20.58 MB</td>
<td>166.79 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Cache in APCu</td>
<td></td>
<td>58.13 MB</td>
<td>190.38 ms</td>
</tr>
<tr>
<td>en_US</td>
<td>Default</td>
<td></td>
<td>31.84 MB</td>
<td>164.26 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (MO)</td>
<td></strong></td>
<td>17.01 MB</td>
<td>118.52 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Ginger MO (JSON)</td>
<td></td>
<td>15.87 MB</td>
<td>120.01 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>DynaMo</td>
<td></td>
<td>58.07 MB</td>
<td>162.41 ms</td>
</tr>
<tr>
<td>de_DE</td>
<td>Cache in object cache</td>
<td><imgsrc="" alt="â " class="wp-smiley" style="height: 1em; max-height: 1em;"></td>
<td>31.86 MB</td>
<td>164.28 ms</td>
</tr>
</tbody>
</table><figcaption class="wp-element-caption">Benchmarks visiting the WordPress admin</figcaption></figure>
<h2 class="wp-block-heading"><strong>Conclusion</strong></h2>
<p>Finding the right path forward means weighing all the pros and cons of each solution and looking at both horizontal and vertical impact, i.e. how much faster can i18n be made for how many sites.</p>
<p>When looking at all these factors, it appears that a <strong>revamped translations parser</strong> (<strong>solution E</strong>) could bring the most significant improvements to all localized WordPress sites. Especially when combined with a <strong>new PHP translation file format </strong>(solution A), which Ginger MO supports, the i18n overhead becomes negligible. Of course the same risks associated with introducing a new format apply.</p>
<p>On top of that, a revamped i18n library like Ginger MO could also be combined with other solutions such as caching or dynamic MO loading to potentially gain further improvements. However, those routes have yet to be explored.</p>
<h2 class="wp-block-heading"><strong>Next steps</strong></h2>
<p>The WordPress performance team wants to further dive into this topic and test some of the above solutions (and combinations thereof) on a wider scale through efforts like the <a href="https://wordpress.org/plugins/performance-lab/">Performance Lab</a> feature project. We are looking forward to hearing your feedback on this analysis and welcome any additional comments, insights, and tinkering.</p>
<hr class="wp-block-separator has-alpha-channel-opacity is-style-dots">
<p>Thank you to <a href="https://profiles.wordpress.org/flixos90/" class="mention"><span class="mentions-prefix">@</span>flixos90</a>, <a href="https://profiles.wordpress.org/westonruter/" class="mention"><span class="mentions-prefix">@</span>westonruter</a>, <a href="https://profiles.wordpress.org/joemcgill/" class="mention"><span class="mentions-prefix">@</span>joemcgill</a>, <a href="https://profiles.wordpress.org/spacedmonkey/" class="mention"><span class="mentions-prefix">@</span>spacedmonkey</a>, and <a href="https://profiles.wordpress.org/adamsilverstein/" class="mention"><span class="mentions-prefix">@</span>adamsilverstein</a> for reviewing and helping with this post. Thank you to <a href="https://profiles.wordpress.org/nbachiyski/" class="mention"><span class="mentions-prefix">@</span>nbachiyski</a>, <a href="https://profiles.wordpress.org/ocean90/" class="mention"><span class="mentions-prefix">@</span>ocean90</a>, <a href="https://profiles.wordpress.org/akirk/" class="mention"><span class="mentions-prefix">@</span>akirk</a>, <a href="https://profiles.wordpress.org/rmccue/" class="mention"><span class="mentions-prefix">@</span>rmccue</a>, <a href="https://profiles.wordpress.org/dd32/" class="mention"><span class="mentions-prefix">@</span>dd32</a> for providing valuable insights and context.</p>
<p class="o2-appended-tags"><a href="https://make.wordpress.org/core/tag/core/" class="tag"><span class="tag-prefix">#</span>core</a>, <a href="https://make.wordpress.org/core/tag/i18n/" class="tag"><span class="tag-prefix">#</span>i18n</a>, <a href="https://make.wordpress.org/core/tag/performance/" class="tag"><span class="tag-prefix">#</span>performance</a></p>
</div>

WordPress 6.9 Beta 2 is now ready for testing! This beta version of the WordPress…
The WordPress Community Team is calling for contributors to help overhaul the tools that power…
As October winds down, so too does Blocktober, Tammie Lister’s month-long challenge to create a…
The WordPress Foundation and WooCommerce have joined Automattic and Matt Mullenweg in countersuing WP Engine,…
The first-ever CloudFest USA Hackathon, taking place November 4 in Miami, will bring together contributors…
Canada’s largest gathering of WordPress enthusiasts drew a strong turnout at Carleton University last weekend,…