David Lewin David Lewin
E-Commerce, Research & Ads

Internationalizing WooCommerce at scale: SaaS (Weglot) vs database-heavy (WPML)

Disclosure: some links in this guide are affiliate links. If you sign up through them, I may earn a commission at no extra cost to you. How this site makes money.

Short answer

WPML (and Polylang) store every translation as its own WordPress post, so each translated variable product copies the product, all of its variations and their meta. In our measurement on WooCommerce 11.2.1, one variable product with 12 variations took 13 posts, about 270 postmeta rows and 24 attribute-lookup rows, and every extra language added the same again: a 10,000-product catalog in 4 languages grows from about 2.7 million to about 10.8 million postmeta rows. Weglot keeps translations in its cloud: WordPress renders the original page, Weglot's plugin catches the HTML in an output buffer and translates it through cdn-api-weglot.com, so MySQL stays the size of one language. The price is a network call on every uncached translated page view, which makes a full-page cache in front of the translated URLs mandatory, and a word quota: Weglot counts unique words across all languages, so a large catalog can outgrow its 5-million-word top plan. Pick Weglot for performance and a lean database; pick WPML when translations must live on your server, when you need per-language prices or stock rules, or when the word count makes SaaS too expensive. Polylang is the open-source version of WPML's model.

Adding a second language to a 200-product shop is a plugin install. Adding four languages to a 10,000-product catalog is an architecture decision, because the two big approaches put the cost in different places. WPML puts it in your database: every translation is another set of rows that every query, backup and stock update has to carry. Weglot puts it on the network: your database stays the same size, but every translated page has to be translated somewhere before a visitor sees it. This guide measures both costs and shows how to keep each one under control.

How this guide was made

We read the source code of the current wordpress.org releases: Weglot 6.4.3, WooCommerce Multilingual (WCML, WPML's WooCommerce add-on) 5.6.3, Polylang 3.9.0 and Linguise 2.2.70. We measured database growth on a real WordPress 7.1.3 and WooCommerce 11.2.1 site by creating variable products and copying each one per language with WooCommerce's own duplicator. That copies the posts and meta the way WPML's model does, but it's a lower bound: WPML also adds its own translation-link rows, translated terms and sync meta. We didn't run WPML's core plugin (it's paid and not on wordpress.org) or a live Weglot account. Prices are from the vendors' pricing pages, checked in October 2026.

1. The short answer

Weglot (SaaS)WPML + WCML (database)Polylang (database, open source)
Where translations liveWeglot's cloudYour MySQL: one post per languageYour MySQL: one post per language
Database growth per languageAlmost noneRoughly a full copy of the catalogRoughly a full copy of the catalog
Work per uncached translated pageRender the original, then an API call to WeglotRender from translated posts, plus language filters on queriesSame as WPML
If the vendor is downOriginal-language HTML, marked uncacheableNothing changesNothing changes
Cost driverUnique words across all languages, and languages per planYearly licence; AI translation creditsFree core; WooCommerce needs a paid add-on
Best forLean database, fast launch, a page cache you controlData on your server, per-language prices and stock, huge word countsSame as WPML on a smaller budget

2. Two ways to serve /fr/

What happens on an uncached request for a French product page
What happens on an uncached request for a French product pageVisitor requests /fr/product/tee/ → WPML: WordPress queries the French post / Weglot: WordPress renders the English page → WPML: HTML goes out / Weglot: POST /translate to cdn-api-weglot.com → Visitor gets the French pageVisitor requests /fr/product/tee/: no page-cache hitVisitor requests /fr/product/tee/no page-cache hitWPML: WordPress queries the French post: language filter on the query, French title, meta and variations from MySQLWPML: WordPress queriesthe French postlanguage filter on the query,French title, meta andvariations from MySQLWeglot: WordPress renders the English page: the plugin catches the HTML in an output bufferWeglot: WordPress rendersthe English pagethe plugin catches the HTMLin an output bufferWPML: HTML goes out: no outside callWPML: HTML goes outno outside callWeglot: POST /translate to cdn-api-weglot.com: text sent in chunks of up to 600 words, French returnedWeglot: POST /translate tocdn-api-weglot.comtext sent in chunks of up to600 words, French returnedVisitor gets the French page: cache it so the next visitor skips all of thisVisitor gets the French pagecache it so the next visitor skips all of this
WPML pays in database rows and query filters. Weglot pays in one network round trip per uncached translated view. A page cache in front hides both, but it matters far more for Weglot.

3. What one-post-per-language does to MySQL

WPML's WooCommerce add-on creates each translated variation with wp_insert_post, links it to the original through WPML's translation table, and records _wcml_duplicate_of_variation in its meta (that's from WCML 5.6.3's variation sync code). Polylang works the same way. So a translated catalog is, row for row, another catalog. We measured how big that is:

TableRows per variable product (12 variations), one languageRows added per extra language
wp_posts1313
wp_postmeta270269
wp_wc_product_meta_lookup1313
wp_wc_product_attributes_lookup2424
wp_term_relationships910

Measured on WooCommerce 11.2.1 with global attributes (colour and size), managed stock and prices on each variation; numbers were the same for 2 and 100 products. Scaled to 10,000 such products:

MySQL rows for 10,000 variable products (millions)
MySQL rows for 10,000 variable products (millions)One language: 2.7 M rows; 4 languages, one post per language (WPML, Polylang): 10.8 M rows; 4 languages, Weglot: 2.7 M rows; One language: 0.59 M rows; 4 languages, one post per language: 2.39 M rows; 4 languages, Weglot: 0.59 M rowswp_postmetaother product tablesOne language: 2.7 M rowsOne language2.7 M rows4 languages, one post per language (WPML, Polylang): 10.8 M rows4 languages, one post per language (WPML, Polylang)10.8 M rows4 languages, Weglot: 2.7 M rows4 languages, Weglot2.7 M rowsOne language: 0.59 M rowsOne language0.59 M rows4 languages, one post per language: 2.39 M rows4 languages, one post per language2.39 M rows4 languages, Weglot: 0.59 M rows4 languages, Weglot0.59 M rows
Projected from our per-product measurement. "Other" is posts, both lookup tables and term relationships. Weglot adds a handful of options rows, not catalog rows.

Row count alone doesn't make a site slow; MySQL handles tens of millions of indexed rows. What hurts is everything that touches every row:

  • Writes multiply. WCML keeps stock in sync across translations, so a sale updates the stock of every language's copy. Product imports and price updates do the same work once per language.
  • Backups, staging copies and migrations grow with the database. A 4-language catalog roughly quadruples the product part of every dump and restore.
  • Meta queries scan more. Admin product lists, layered-nav filters (the attribute lookup table) and plugins that query postmeta without good indexes work over four times the rows.
  • Every query gets a language filter. WPML joins its translation table to keep French products off English pages. That's cheap with an object cache and warm buffers, and noticeable without them.

To see where your own WPML store stands, count translated rows per language:

-- Products and variations per language in a WPML store (adjust the wp_ prefix)
SELECT t.language_code,
       SUM(t.element_type = 'post_product')           AS products,
       SUM(t.element_type = 'post_product_variation') AS variations
FROM wp_icl_translations t
WHERE t.element_type IN ('post_product', 'post_product_variation')
GROUP BY t.language_code;

-- And how much meta those rows carry
SELECT t.language_code, COUNT(*) AS postmeta_rows
FROM wp_icl_translations t
JOIN wp_postmeta m ON m.post_id = t.element_id
WHERE t.element_type IN ('post_product', 'post_product_variation')
GROUP BY t.language_code;

4. What SaaS does to every request

Weglot's WordPress plugin doesn't store translations in WordPress. Reading its source (6.4.3) shows the path:

  • Output buffer. On a translated URL, the plugin starts ob_start() and lets WordPress render the original page as usual.
  • One API call per uncached view. The parser extracts the text, skips excluded blocks, and posts it to cdn-api-weglot.com/translate in chunks of up to 600 words. Weglot's servers return the stored translation (or translate new strings). The plugin has no local translation cache: without a page cache, every French page view is a round trip.
  • Timeouts are generous. The bundled HTTP client defaults to an 80-second timeout and a 30-second connect timeout, and the plugin doesn't lower them. A slow API means slow pages and busy PHP workers.
  • Failures fail open. On an API error the plugin returns the original-language HTML on the translated URL, defines DONOTCACHEPAGE and sends no-cache headers, so the untranslated page isn't cached under the French URL. Visitors see English until the API recovers.
  • Billing follows views. Weglot plans include a monthly number of translation requests that Weglot says roughly equals translated page views. Your cache hit ratio is your bill.

So the database stays small, but uncached translated pages cost a network round trip and a little quota each. At scale, Weglot only works well with a full-page cache in front of the translated URLs. Linguise, the other SaaS covered in our multilingual SEO guide, does keep a local file cache, but it's keyed on the rendered original HTML, so WordPress still renders the page every time.

5. Caching translated pages

Weglot serves languages in subdirectories (/fr/, /de/), which is the friendliest layout for caches: each language is a different URL, so it gets its own cache entry with no extra rules. Three things still go wrong:

  • Don't vary on Accept-Language or cookies. The language is in the path. Varying on headers splits the cache into hundreds of copies and drops the hit rate, which with Weglot means more API calls.
  • Browser-language redirects aren't cacheable. If you enable Weglot's automatic redirect, the redirect on the original URL depends on the visitor's browser. Use the JavaScript variant (weglot_autoredirect_js) or exclude that response from the cache, or a French visitor's redirect will be served to everyone.
  • Purge every language. Most cache plugins purge the URL of the product you edited, which is the English one. The French copy keeps the old price. Purge all of a product's URLs when it changes:
<?php
/**
 * Plugin Name: Purge translated product URLs
 * When a product changes, purge its URL in every Weglot language, not just the original.
 * Replace store_purge() with your cache's purge call; the PURGE request below suits Varnish.
 */
function store_purge( $url ) {
	wp_remote_request( $url, [ 'method' => 'PURGE', 'blocking' => false, 'timeout' => 2 ] );
}

add_action( 'woocommerce_update_product', function ( $product_id ) {
	$url = get_permalink( $product_id );
	if ( ! $url ) {
		return;
	}
	if ( ! function_exists( 'weglot_create_url_object' ) ) {
		store_purge( $url );
		return;
	}
	// getAllUrls() is what Weglot uses to print hreflang: one entry per language, translated slugs included.
	foreach ( weglot_create_url_object( $url )->getAllUrls() as $alt ) {
		if ( empty( $alt['excluded'] ) ) {
			store_purge( $alt['url'] );
		}
	}
} );

Stock changes from orders don't fire woocommerce_update_product. If you show stock levels on cached product pages, hook woocommerce_product_set_stock and woocommerce_variation_set_stock too, or keep stock out of the cached HTML and load it with JavaScript. Our WooCommerce caching guide covers the cache side in depth.

6. Hreflang in each model

Both plugins print the <link rel="alternate" hreflang> set for you. From Weglot's source, two behaviours are worth knowing:

  • It drops the hreflang set on non-canonical pages. If a page's canonical points elsewhere (a filtered or paginated URL), Weglot removes its alternates, which is correct: hreflang belongs on canonical URLs only.
  • It leaves out noindexed languages. A comment in the code explains why: an hreflang pointing at a noindex page is a contradictory signal and can invalidate the whole cluster.

With WPML the alternates come from the translated posts, so a product that isn't translated into one language simply has no alternate for it. The classic WPML problem is the other way round: an SEO plugin printing a canonical that points every language to the original URL, which cancels hreflang. Whichever you choose, crawl a sample of product clusters after launch. The multilingual SEO guide has a script that checks return links, canonicals, status codes and noindex across each cluster.

7. The word bill at 10,000 products

SaaS translation is priced by words, and catalogs are wordy. Weglot's plan limit is "total unique words translated across all your languages", and it doesn't renew: it's the size of what's stored.

Weglot planWordsLanguagesPer year
Business50,0003€290
Pro200,0005€790
Advanced1,000,00010€2,990
Extended5,000,00020€6,990
EnterpriseCustomCustomOn request

A catalog of 10,000 products at 120 unique words each is 1.2 million words per language. Four target languages make 4.8 million: the top of the Extended plan. Product descriptions that share boilerplate (care instructions, shipping notes) count once, which helps, but a catalog with long unique descriptions can outgrow every published plan. WPML's model has no word quota: the Multilingual CMS plan is €99 a year (WooCommerce support included, with 22,500 AI-translation words, topped up at renewal), and more automatic translation is bought as credits. You can also translate with your own team or agency at no per-word platform fee.

Measure before you choose: the multilingual SEO guide includes a script that counts your catalog's total and unique words through the WooCommerce REST API and names the smallest plan that fits.

8. When each one wins

  • Weglot when you want the catalog database to stay one language, you can put a page cache in front of every translated URL, and your unique word count fits a plan. Launching is fast: no translated posts to create, sync or keep in step.
  • WPML when translations must stay on your server (contracts, data residency, offline resilience), when languages need different prices, stock rules or products, or when the word count makes SaaS pricing painful. Budget for a stronger database and an object cache.
  • Polylang when you like WPML's model but want an open-source core. The free plugin doesn't translate WooCommerce products: it shows a notice recommending the paid Polylang for WooCommerce add-on.

9. Our opinion: trade-offs

  • For a catalog in the thousands, the database cost of WPML is real but manageable; the operational cost (four copies to import, sync and back up) is what teams feel. Weglot removes that work entirely.
  • Weglot without a page cache is the worst of both worlds: a network call on every translated view and a request quota that follows your traffic. With a good cache it's the fastest setup to run.
  • Do the word maths first. It's the one thing that can rule Weglot out for a very large catalog, and it takes ten minutes to measure.
  • Don't translate private pages. Account and order-received pages have no search value and carry customer data. Keep them in the original language; the SEO guide has a mu-plugin for that.

10. Tools

Top picks

ToolRoleWhy it fits
WeglotSaaS website translationOur pick for performance: translations live in Weglot's cloud, the catalog database stays one language, and translated pages are ordinary subdirectory URLs your cache can hold. WooCommerce price and address blocks are excluded by default.
WPMLSelf-hosted WordPress multilingualFor stores that need translations on their own server, per-language product control or no word quota. The CMS plan includes WooCommerce support.

Alternatives: free tools and references

We don't earn anything from these links.

11. Pitfalls

  • Testing on a 50-product staging site. Both models look free there. Measure on a copy of the real catalog.
  • Purging only the original URL. The translated copies keep stale prices until their cache expires.
  • Caching the auto-redirect. One visitor's language becomes everyone's.
  • Translating everything. SKUs, prices, addresses and account pages don't need translating; excluding them saves words and keeps customer data at home.
  • Switching models later. Moving from WPML to a SaaS (or back) means re-creating every translation and every translated URL. Decide with the full catalog in mind.
Bottom line

WPML and Polylang turn every language into another copy of the catalog in MySQL: in our measurement about 270 postmeta rows per variable product per language, so 10,000 products in four languages means roughly 10.8 million postmeta rows. Weglot keeps MySQL at one language and translates in its cloud, at the cost of a network call on every uncached translated page and a quota of unique words. Choose Weglot for performance, put a full-page cache in front of every language and purge all of them together. Choose WPML when the data must live on your server or your word count is too big for SaaS pricing.

David Lewin
David Lewin

Founder of StealthScale.io and e-commerce infrastructure engineer writing field-tested playbooks on proxies, cloud VPS, anti-detect browsers and SEO.