Plugin documentation

Multi Currency for Polylang and WooCommerce

Give every Polylang language its own WooCommerce currency. Prices, shipping, free-shipping limits, fixed coupons and the order itself follow the currency of the language the shopper is in.

Version 2.3.0 WooCommerce plugin Paid plugin Requires WordPress 6.5 Requires PHP 7.4 Tested up to 7.1 Updated 6 Oct 2026

1. Overview

Multi Currency for Polylang and WooCommerce gives each Polylang language its own WooCommerce currency. A shopper browsing your French pages sees prices, shipping and checkout in the currency you chose for French. The German pages can use another currency, and a language with no currency of its own keeps the store currency. The plugin is listed on the Plugins screen as "WXP Multi Currency for Polylang and WooCommerce".

The shopper does not pick a currency. The language decides. If you want a separate currency drop-down that is independent of language, this plugin is not the right tool (see What it does not do).

How it works

  1. On the Polylang language screen you choose a WooCommerce currency for each language and, for any currency other than the store currency, type its exchange rate (1 store currency = X of the other currency).
  2. On a page in that language, WooCommerce reports the language's currency (symbol, decimals, separators). Each translated product keeps its own price, which you type in the language's currency. Optionally, the plugin can calculate the price from the default-language product with your rate instead.
  3. The rate also converts shipping costs, shipping taxes, the free-shipping minimum and, by default, fixed coupon amounts. Fixed fees added by other plugins can be converted too, but that is off by default.
  4. A cart holds products of one currency only. When the shopper switches to a language with another currency, shop, cart and checkout pages send them back to the language of their cart.
  5. Classic checkout and the Cart and Checkout blocks create the order in the currency of the cart and save the order language.

At a glance

Currency per language

A Currency and an Exchange rate field on Polylang's Add and Edit language screens. Empty means the store currency.

Own price or converted price

By default every translation uses the price you typed on it. An optional mode converts the default-language price of simple products. A per-product checkbox keeps the product's own price.

Shipping, coupons, fees

Shipping costs, shipping taxes, the free-shipping minimum and fixed coupons are converted with the rate. Fixed fees can be converted on request.

One currency per cart

Products priced in another currency are refused. Languages that share a currency share a cart.

Classic and block checkout

Orders are created in the cart's currency with the order language saved. Works with High-Performance Order Storage.

Number format per currency

Decimals, symbol position and separators for every currency, with a live preview. Zero-decimal and three-decimal currencies get the right number of decimals automatically.

What it does not do

  • It has no currency switcher, shortcode, widget or block. The currency follows the language, so Polylang's own language switcher is the only switch.
  • It does not fetch exchange rates from the internet. You type every rate, and you keep them up to date.
  • It does not choose a currency from the visitor's country or browser. Only the Polylang language counts.
  • It does not convert prices of variable products or variations. They always use the prices entered on the translation.
  • It does not convert percentage coupons, tax rates or amounts that other plugins add or calculate, except fixed fees when you switch that on.
  • It does not synchronise prices between translations. It switches off the price synchronisation of Polylang Pro and Polylang for WooCommerce so that each translation keeps its own price.
  • It does not talk to a payment gateway. The order is created in the cart currency, and payment gateways read the order as WooCommerce stores it.
  • It has no REST routes of its own, no WP-CLI commands, no AJAX endpoints, no scheduled events and no database tables.

2. Requirements

ItemRequirementNotes
WordPress6.5 or laterFrom Requires at least in the plugin header. readme.txt says tested up to 7.1. The header also has Requires Plugins: woocommerce: in WordPress 7.1.2 that stops the plugin from being activated while WooCommerce is missing or inactive. The Plugins screen then says "This plugin cannot be activated because required plugins are missing or inactive." under the plugin, and an activation attempt fails with "Error: ... requires 1 plugin to be installed and activated: ...". Only WooCommerce is declared this way; Polylang is checked by the plugin itself.
PHP7.4 or laterFrom Requires PHP in the plugin header.
WooCommerce8.0 or laterFrom WC requires at least; tested up to 11.1 (WC tested up to). The plugin declares compatibility with High-Performance Order Storage and with the Cart and Checkout blocks.
PolylangPolylang or Polylang Pro, requiredThe plugin only starts when Polylang is active. It does not declare a minimum Polylang version. The 2.1.0 changelog says it was tested with Polylang 3.8.
Polylang for WooCommerceOptionalWithout it, make Products translatable in the Polylang settings and translate the Shop, Cart, Checkout and My account pages. With it, Polylang for WooCommerce handles those pages and the order language and this plugin only keeps the language of the order in step. See Polylang integration.
User permissionmanage_woocommerce for WooCommerce → Currency RateThe currency fields on the Polylang language screen are shown to whoever can open that Polylang screen. The licence screen needs manage_options.
ServerNothing specialThe plugin makes web requests only for the licence and update check (see Privacy and data). It works without a licence.

When Polylang or WooCommerce is not active, the plugin shows this notice to users who can activate plugins and does nothing else:

WXP Multi Currency for Polylang and WooCommerce needs Polylang (or Polylang Pro) and WooCommerce to be active.

In that state the licence screen and the update check are not loaded either, because they start together with the plugin.

3. Installation

  1. Make sure WooCommerce and Polylang (or Polylang Pro) are installed and active.
  2. Open Plugins → Add New Plugin → Upload Plugin, choose the zip file from your wpexpertshub.com account and click Install Now, then Activate.
  3. Activate your licence so that updates arrive (see Licence and updates). This step is optional for the plugin to work.
  4. In the Polylang settings, make Products (and product categories and tags) translatable, or install Polylang for WooCommerce.
  5. Follow the Quick start.

On the Plugins screen the plugin adds two links under its name: Currency Rate (opens WooCommerce → Currency Rate) and Activate License or Deactivate License (opens the licence screen). The wording of the second link depends on the stored licence status, which is kept under a name built from the plugin's folder name. Keep the folder name wxp-polylang-multi-currency, or the link may always say "Activate License".

Licence and updates

The plugin bundles the WpExperts Hub licence and update client (licence/class-wpxh-licence-client.php, version 2.0.0). One screen manages the licences of every WpExperts Hub plugin on the site.

  1. Open Plugins → WpExperts Hub Licences. You need the manage_options capability.
  2. Find "Multi Currency for Polylang and WooCommerce", paste the licence key from your purchase email (also shown in My Account → Downloads on wpexpertshub.com) and click Activate licence. The key field keeps only letters, digits and hyphens.
  3. When the key is accepted, the card shows Active and the key in masked form (first four and last four characters; a key of 10 characters or fewer is shown in full). Updates now appear on the normal Dashboard → Updates and Plugins screens.
  4. To move the plugin to another live site, click Deactivate licence first. The licence is always removed from this site, even if wpexpertshub.com cannot be reached (the message is then "Licence removed from this site.").
  5. Lost your key? Open "Can't find your key? Email it to me", enter the email address used for the purchase and click Send licence key.

Without an active licence the plugin works in full. The only thing the key does in this plugin's code is let wpexpertshub.com hand out the update package: the client uses the package address that the server returns. When an update exists but no package comes with it, WordPress shows "Automatic update is unavailable for this plugin. Activate your licence to enable updates." under the plugin. Administrators also see "Activate your WpExperts Hub licence to receive updates for: ..." with a Manage licences link on the Plugins screen while any registered WpExperts Hub plugin has no active licence.

Update information is requested in one batched call for all WpExperts Hub plugins and cached for 12 hours (one hour after a failed request). The cache is cleared when you activate or deactivate a licence, ask for your key, or finish a plugin update. See Privacy and data for exactly what is sent.

The licence server address can be changed for development with the WPXH_LICENCE_SERVER constant or the wpxh_licence_server filter.

What activation creates

Nothing. The plugin has no activation routine: no tables, no options, no roles, no scheduled events and no pages. Settings are created when you first save a language currency or the WooCommerce → Currency Rate screen. Until you set a currency on a language, every language uses the store currency and the plugin changes nothing.

Updating and what is kept

Update from Dashboard → Updates or the Plugins screen. Saved settings are kept. Version 2.3.0 stores exchange rates per currency; rates saved by version 2.1 (one per language) are carried over once, the first time they are read. The old per-language option is still written as well, so a downgrade keeps working.

Multisite

All settings are per site (stored with get_option() and term meta). There is no network settings screen. On uninstall the plugin visits every site of the network and removes its data (see Privacy and data).

Deactivating and deleting

Deactivating keeps all data, and every language goes back to the store currency. Orders already placed keep the currency they were created with. Deleting the plugin from the Plugins screen removes the settings, the currency chosen on each language and the "Use Product Price" flags. It keeps the language stored on each order and the licence data.

4. Quick start

  1. Check that your store currency is right under WooCommerce → Settings → General. Rates are counted from this currency.
  2. Open Languages → Languages in the Polylang menu and click Edit on a language. Choose its Currency. For any currency other than the store currency, type the Exchange rate in the form 1 USD = [ ] EUR (with your own currency codes). Click Update. Repeat for each language that needs its own currency.
  3. Open WooCommerce → Currency Rate. Check that the summary says All rates set, review the number format of each currency and read the preview.
  4. Translate your products. On each translation, type the price in the currency of its language. Or choose Convert the default-language price with the rates above on the Currency Rate screen and save.
  5. Without Polylang for WooCommerce, translate the Shop, Cart, Checkout and My account pages with Polylang.
  6. Test: add a product on one language, switch language, and check that you are sent back to the cart's language. Place a test order and check the order's currency and total.

5. Features

Currency and exchange rate on each language

The plugin adds two fields to Polylang's language form, on both Add new language and Edit language:

  • Currency: a list of every WooCommerce currency shown as EUR – Euro (€), with Store currency (USD) first (the label shows your own store currency). That first entry saves an empty value, and the language then uses the store currency. Choosing the store currency code from the list behaves the same way. Help text: "Prices, shipping and checkout of this language use this currency. Leave on the store currency for the default language."
  • Exchange rate: shown only when a currency other than the store currency is selected, as 1 USD = [ ] EUR. Help text: "Used for shipping costs, free shipping limits and fixed coupons. Shared by every language that uses this currency. More options (number format, preview) are on the Currency Rate screen." The field is filled with the rate already saved for that currency, and changing the currency selector fills in the saved rate of the new currency unless you typed one yourself.

What happens when you save:

  • The currency code is stored on the language (term meta _wxp_poly_currency, upper case). Only codes known to WooCommerce are accepted. An unknown code is ignored and the old value stays.
  • A rate is saved only for a currency other than the store currency. A comma is accepted as the decimal point. An empty field keeps the rate that is already saved for that currency. A value that is not a number, or is zero or negative, is not saved and Polylang shows the warning "The exchange rate for EUR must be a number greater than zero. It was not saved."
  • Rates belong to the currency, not to the language. French, German and Spanish priced in EUR share one rate.
  • A language that is added or changed from code, the Polylang REST API or the setup wizard without a currency field keeps its current currency (the field is only applied when the form field is present in the request).

On the Polylang languages screen administrators with manage_woocommerce also see two warnings when something needs attention: that a currency has no exchange rate yet, and that the store currency changed after the rates were entered (see The Currency Rate screen).

How the currency of a page is chosen

The plugin hooks woocommerce_currency at priority 999. It asks "which language decides for this request?" and returns that language's currency. If the language has none, the value WooCommerce had is left alone.

  • Normal page requests: the language of the page (Polylang's current language).
  • Cart and checkout background requests (WooCommerce wc-ajax calls and the Store API used by the Cart and Checkout blocks): these requests do not carry a reliable language in the address, so the language of the cart decides. The exception is a request that names its own language: it is kept when that language is priced in the same currency as the cart. A German shopper with a French (EUR) cart therefore orders in German and EUR.
  • Empty cart on a background request: the current language.
  • No current language on a normal request (Polylang reports none, for example on a page it does not translate): the language of the cart, when the cart has one.
  • In wp-admin (outside AJAX): the language Polylang treats as current. In Polylang 3.8 this is the language chosen in the admin language filter, or the language of the post or term you are editing. With "Show all languages" there is no current language and the store currency applies. WooCommerce passes an order's own currency when it formats order totals (for example get_formatted_order_total() in WooCommerce 11.1.2), and the plugin applies that currency's number-format overrides to such amounts (see Number format per currency).

Because the plugin sets the currency at priority 999, another plugin that changes woocommerce_currency at an earlier priority is overridden whenever the current language has a currency. Do not run another currency-switching plugin next to this one.

The Currency Rate screen

Open WooCommerce → Currency Rate (menu and browser title "Currency Rate", heading on the screen "Currency Rates"; capability manage_woocommerce). The screen groups languages by the currency they use.

  • Introduction: states the store currency and explains that rates are counted from it. A link opens WooCommerce → Settings → General.
  • Summary chips: number of languages, number of currencies, and either "Rate missing: EUR, KRW" or "All rates set".
  • Notices: a warning for currencies that have no exchange rate yet ("EUR has no exchange rate yet. Until you enter one, shipping costs and fixed coupon amounts are shown unconverted for the languages that use it."), and a warning when the store currency changed after the rates were saved ("The store currency is now USD, but the exchange rates were entered when it was EUR. Rates must be counted from the store currency: check every rate and save the screen again.").
  • Table "Languages, currencies and rates": one row per currency. The store currency comes first and is marked Store currency; its rate is fixed at 1. Each other row shows the currency code, symbol and name, the languages that use it (each links to its Edit language screen; the default language is marked Default), the Exchange rate field, a Number format panel ("Decimals, symbol, separators") and a Preview.
  • Preview amount: a field above the table (default 100, minimum 0). The preview shows the amount in the store currency, an arrow, and the same amount converted with the row's rate and formatted with the row's number format. The preview is calculated by the page's script in your browser, for illustration only. Under the rate field the screen shows "Enter an exchange rate" for a missing rate and "The rate must be a number greater than zero" for a bad one, and the preview shows a dash until the rate is valid. When the two separators clash the preview says "The thousands and decimal separators must differ".
  • Product prices, Coupons and fees: option groups described under Product prices and Coupons and fees.

If Polylang has no languages, the screen only says "No languages yet" with an Open Languages button.

Click Save to store everything on the screen. The form is protected by a nonce and by the manage_woocommerce capability, and it redirects after saving so that reloading does not submit it again. You see "Currency Rate Saved!" and, if some values were refused, "Some values were not saved:" followed by the reasons. Details of each value are in Settings.

Saving rates also makes WooCommerce recalculate shipping for carts that already exist. WooCommerce keeps shipping quotes in the customer's session; the plugin raises the shipping cache version so that new quotes use the new rate.

Exchange rates, conversion and rounding

A rate is always "1 store currency = X units of the other currency". If the store currency is USD and the French language uses EUR at 0.92, a shipping cost of 10 (typed in USD) becomes 9.20 EUR. Rates are stored as the text you typed (after turning a comma into a dot), with no rounding.

CarefulA currency without a valid rate is treated as rate 1. Shipping, free-shipping limits and coupons are then used as typed, with no conversion. In "Convert" price mode the default-language price is copied as the same number. Fix the warning on the Currency Rate screen before you sell.

The plugin applies the rate in these places. Each one has its own rounding, set out here so that totals can be checked by hand.

AmountCalculationRounding
Product price (Convert mode only)Price of the default-language product × (rate of the product's own language ÷ rate of the default language)The result of the multiplication is rounded to 8 decimals first, then formatted to the number of decimals of the target currency (PHP number_format rounding, halves go up). Example: 17.99 × 1350 is 24286.499999999996 in floating point; after the 8-decimal step it is 24286.5, and with 0 decimals it becomes 24287.
Shipping costCost × rateRounded to 6 decimals. WooCommerce then calculates and rounds the cart totals itself, using the decimals of the currency in force.
Shipping tax (each tax line)Tax × rateRounded to 6 decimals.
Free-shipping minimumMinimum order amount × rateRounded to the currency's decimals, compared with the cart subtotal rounded the same way. Free shipping applies when the subtotal is greater than or equal to the minimum.
Fixed coupon amount (fixed cart, fixed product)Amount × rateRounded to 8 decimals and stored as a decimal string. WooCommerce rounds the resulting discount.
Coupon minimum and maximum spend (all coupon types)Amount × rateRounded to 8 decimals, then formatted to the currency's decimals.
Fees added by other plugins (only when switched on)Amount × rateRounded to 8 decimals.

The "rate" in each row is the rate of the language that decides for the request (see How the currency of a page is chosen). When that is the store currency, or when the currency has no rate, the factor is 1 and nothing is changed. Order totals are not recalculated by the plugin: WooCommerce calculates them from the converted pieces, with the decimals of the currency in force (see Number format per currency).

Number format per currency

For every currency you can set decimals, symbol position, thousands separator and decimal point in the Number format panel of its row. A field left on "Store setting" follows WooCommerce → Settings → General. The plugin applies the format by filtering the WooCommerce price settings (wc_get_price_decimals, wc_get_price_thousand_separator, wc_get_price_decimal_separator, woocommerce_price_format, all at priority 20) for the currency of the current request. Because WooCommerce calculates carts in units of wc_get_price_decimals(), the decimals of the currency also decide how cart lines and totals are rounded.

  • Automatic decimals. For a currency other than the store currency, these zero-decimal currencies are shown and charged with 0 decimals unless you choose otherwise: BIF, CLP, DJF, GNF, ISK, JPY, KMF, KRW, PYG, RWF, UGX, UYI, VND, VUV, XAF, XOF, XPF. These three-decimal currencies get 3 decimals: BHD, IQD, JOD, KWD, LYD, OMR, TND.
  • The store currency always follows the WooCommerce setting unless you override it on this screen. If your store currency is JPY and WooCommerce says 2 decimals, the automatic rule does not change that.
  • Symbol position. The WooCommerce option woocommerce_currency_pos is also filtered for the request, because the block Cart and Checkout (Store API) read it directly. Inside wp-admin screens (outside AJAX) it is left alone so that saving the WooCommerce settings screen does not store a language's choice.
  • Other currencies on one page. When code formats an amount with an explicit currency that differs from the request's currency (order screens, emails, REST), the plugin applies that currency's own number-format overrides (its saved format, or the automatic decimals of a zero-decimal or three-decimal currency). It changes only the values the caller left at the request's defaults. A currency that has no overrides keeps the number format of the request's currency.

Product prices

Two modes, chosen under Product prices on the Currency Rate screen:

  • Use the price entered on each translated product (own, the default, recommended). Prices are never touched. Type the price in the currency of the language on every translation, so you control prices such as 9.99 or 1,000. A translation with no price has no price.
  • Convert the default-language price with the rates above (convert). For a product in a language that is not the default language, the price, regular price and sale price are taken from the product's translation in the default language and converted as described in Exchange rates, conversion and rounding.

In Convert mode, conversion applies only when all of these are true:

  • the product is a simple or external product (variable products and variations always use their own prices). External products are converted too but have no Use Product Price checkbox, so they cannot opt out;
  • the product is in a language other than Polylang's default language and has a translation in the default language;
  • the product does not have Use Product Price ticked.

An empty source price stays empty. The source price is read directly from the default-language product (the stored value, before filters), so a sale there flows through to the translation. When both languages share a currency the factor is 1, so a translated simple product still takes the default-language price.

Use Product Price. On the product edit screen, in Product data next to Virtual and Downloadable, simple products get a checkbox Use Product Price. Its description reads "When prices are converted from the default language (WooCommerce > Currency Rate), keep this product's own price instead." It is stored as the product meta _wxp_poly_type (yes, or removed when unticked) when the product is saved from the edit screen, and it has an effect only in Convert mode. It does not appear in WooCommerce's block-based product editor: the plugin declares itself incompatible with that editor.

Prices are never synchronisedThe plugin removes the price fields (_price, _regular_price, _sale_price, and the variation price summary fields such as _min_variation_price and _max_variation_price) from what Polylang for WooCommerce copies between translations (filter pllwc_copy_post_metas, priority 999), and the three price fields from Polylang Pro's synchronised fields (pll_sync_post_fields, priority 999). A translation created by copying keeps its own price.

Shipping

Shipping quotes are converted on woocommerce_package_rates (priority 100): each rate's cost and each of its tax amounts are multiplied by the rate of the deciding language. Nothing happens when that rate is 1.

  • Enter shipping costs in the store currency. Flat rate costs, table rates and similar settings are typed once, in the store currency, and the plugin converts them.
  • A method that already quotes in the cart's currency (for example live carrier rates) would be converted twice. Return false from the wxp_poly_convert_shipping_rate filter for that method (see Filters and actions the plugin fires).
  • Free shipping minimum. The plugin takes over the availability check of WooCommerce's Free shipping method (woocommerce_shipping_free_shipping_is_available, priority 999) whenever the rate is not 1. It repeats WooCommerce's logic for the Free shipping requires options "A valid free shipping coupon", "A minimum order amount", "A minimum order amount OR coupon" and "A minimum order amount AND coupon", with the Minimum order amount multiplied by the rate. It honours Coupons discounts: Apply minimum order rule before coupon discount (the discounts are subtracted from the subtotal unless that box is ticked) and the tax display setting, as WooCommerce does. A result that another plugin set at an earlier priority is replaced in this case. In WooCommerce 11.1.2 the plugin's logic matches the core method's. When the rate is 1 the WooCommerce result is returned unchanged.

Coupons and fees

Two option groups on the Currency Rate screen, both under Coupons and fees.

  • Fixed coupons, default Convert with the rate. A "fixed cart discount" or "fixed product discount" coupon of 10 (typed in the store currency) takes off the equivalent in the language's currency. Its minimum spend and maximum spend are converted too, for every coupon type. Percentage coupons are never changed. Use the amounts as entered switches this off: a coupon of 10 takes off 10 in whatever currency the cart is in. The conversion is done when WooCommerce reads the coupon values (woocommerce_coupon_get_amount, woocommerce_coupon_get_minimum_amount, woocommerce_coupon_get_maximum_amount, priority 20). It is skipped inside wp-admin screens (outside AJAX) so that the coupon edit screen shows what you typed.
  • Fees added by other plugins, default Use the amounts as entered. Fees that are a percentage of the cart are already calculated in the cart currency, so converting them would be wrong. Choose Convert with the rate only for fixed fees typed in the store currency, such as a cash-on-delivery or handling fee. The plugin multiplies each fee on woocommerce_cart_calculate_fees at priority 9999. Return false from wxp_poly_convert_fee to keep one fee unconverted.
CarefulIf you turn on fee conversion, fees that are already percentage-based or that are already calculated in the cart currency will be multiplied by the rate a second time.

One currency per cart

A cart can hold products priced in one currency only. The plugin remembers the language of each cart item (cart item data poly_lang) and the language of the cart (WooCommerce session key wxp_poly_cart_lang, taken from the first item that has a language).

  • Adding a product from another currency is refused with the error "Empty your cart before you add item in different currency." The comparison is by currency, so two languages that share a currency (French and German in EUR, or a language with no currency and the store-currency language) can share a cart.
  • Redirect. On shop, product, cart and checkout pages (template_redirect, priority 999), if the page's language is priced in another currency than the cart, the shopper is sent to the same page in the cart's language (the URL Polylang uses for the language switcher), with the same error notice. If there is no such URL, the plugin tries (only without Polylang for WooCommerce) the shop archive link in that language, and then the translated Shop page. If none of these gives an address, the shopper is not redirected. The "order received" and "pay for order" pages are never redirected.
  • Repairing a mixed cart. If a cart is loaded with products of two currencies (a saved cart merged at login, for example), the plugin keeps the products of one currency, removes the others and shows "Some products were removed from your cart because they are priced in a different currency." The currency kept is the one of the page the shopper is on when that currency is in the cart, otherwise the currency of the first item. No message is shown on background requests.
  • Polylang for WooCommerce cart translation is switched off. Polylang for WooCommerce normally swaps the cart's products for their translations in the language you browse. That would put a product priced in another currency into the cart. The plugin returns false from the Polylang for WooCommerce filter pllwc_enable_cart_translation (priority 20). To leave Polylang for WooCommerce in charge of the cart, return false from the plugin's filter wxp_poly_keep_cart_untranslated.

Cart amounts on pages of another language

A shopper with a French (EUR) cart can read a Korean (KRW) page. The header cart and other cart amounts on that page must still say EUR. The plugin re-renders these WooCommerce strings in the cart's currency when the page's currency is different: cart subtotal, cart contents total, cart total, a cart line's price and a cart line's subtotal (priority 999). It does nothing in wp-admin screens outside AJAX.

Checkout and orders

WooCommerce stores the order's currency from get_woocommerce_currency() (in WooCommerce 11.1.2: WC_Checkout::create_order() for classic checkout, OrderController::update_order_from_cart() for the Store API). Because the plugin sets that currency from the cart's language, both checkout types create the order in the currency the cart was priced in. Shipping is converted on the same requests with the same rate.

  • Order language. The language the order was placed in is saved as the order meta _wxp_poly_lang, through the order object (compatible with High-Performance Order Storage), on woocommerce_checkout_create_order and woocommerce_store_api_checkout_update_order_meta.
  • With Polylang for WooCommerce. That plugin stores its own language on every order and uses it for emails, the thank-you page and reports. A block checkout request that carries no language would give the order the default language. The plugin copies the order's real language into Polylang for WooCommerce's order language (priority 5 on woocommerce_checkout_order_processed and woocommerce_store_api_checkout_order_processed) when the two differ. Errors in that step are swallowed so a checkout is never blocked.
  • Without Polylang for WooCommerce. The customer is sent to the "order received" page of the translated Checkout page in the language of the order (filter woocommerce_get_checkout_order_received_url, priority 20, adds the order key).
  • Payment. The plugin does not talk to payment gateways. A gateway reads the order's stored total and currency. Check with your gateway that it supports the currencies of your languages.

Polylang integration

The plugin starts on plugins_loaded at priority 20, when Polylang's POLYLANG_VERSION constant is defined (or the Polylang class exists) and WooCommerce is loaded. It uses these Polylang hooks and functions:

  • pll_language_add_form_fields and pll_language_edit_form_fields (priority 999) to show the Currency and Exchange rate fields.
  • pll_add_language and pll_update_language (priority 999) to save them. These hooks also run for the Polylang REST API, the wizard and code.
  • pll_current_language(), pll_default_language(), pll_get_post_language(), pll_get_post() and PLL()->model to find languages and translations. pll_add_notice() is used for the rate warning when it exists.
  • PLL()->links->get_translation_url() for the redirect to the cart's language.

Pages without Polylang for WooCommerce. When PLLWC_VERSION is not defined, the plugin filters woocommerce_get_shop_page_id, woocommerce_get_cart_page_id, woocommerce_get_checkout_page_id, woocommerce_get_myaccount_page_id and woocommerce_get_terms_page_id (priority 20) to return the translation of the page in the deciding language, and woocommerce_get_shop_page_permalink to return the translated Shop page (for example /fr/boutique/), or the shop link with the language added. Translate these pages in Polylang, otherwise the untranslated page is used.

Related products. The related-products list of a product contains only products of the same language (or products with no language), so a Korean product page does not list French EUR products (woocommerce_related_products, priority 20).

Block cart, Store API and the admin product list

  • Store API product responses (/wc/store/v1/products and /wc/store/products, filter rest_post_dispatch, priority 20). Every product is priced in the currency of its own language, whatever language the request carries. The plugin asks the Store API to price the product again with that currency in force. If that is not possible (older WooCommerce), it rewrites the prices block itself: it rescales price, regular_price, sale_price and the price range to the currency's decimals and sets the currency code, symbol, separators, prefix and suffix. Other Store API routes and the WooCommerce REST API (/wc/v3/) are not changed.
  • Admin product list. The price column shows each product in the currency of its own language (woocommerce_get_price_html, priority 999), with variable and sale price ranges intact. This only differs from the standard display when the product's language currency is not the one in force for the screen.

6. Settings

All settings live in three places: the Polylang language form, the Currency Rate screen and the product edit screen. There is no separate settings page. Defaults apply until you save for the first time.

Polylang language form

Setting (label)KeyDefaultWhat it does
CurrencyTerm meta _wxp_poly_currency (form field term_currency)Empty = store currencyA currency code known to WooCommerce, upper case, or empty. Prices, shipping and checkout of this language use it. An unknown code is not saved.
Exchange rateOption wxp_currency_rates (form field term_currency_rate)Empty (no rate)1 store currency = X of the selected currency. Must be a number greater than zero; a comma counts as a dot. Empty keeps the saved rate. Not shown for the store currency. Shared by every language with this currency.

Currency Rate screen: one row per currency

Setting (label)KeyDefaultWhat it does
Exchange ratewxp_currency_rates (field wxp_currency_rates[CODE])Empty (no rate)1 store currency = X of this currency. Number greater than zero, comma or dot as the decimal point, no thousands separator. An empty field removes the saved rate. A bad value is refused with "The exchange rate for EUR must be a number greater than zero. The previous rate was kept." Do not use exponent notation: 1e3 would be stored as 13. Not editable for the store currency (always 1).
Decimalswxp_currency_formats[CODE][decimals]"Default (N)": the store's setting; 0 for the zero-decimal currencies and 3 for the three-decimal currencies listed under Number format per currency, when the currency is not the store currency0, 1, 2, 3 or 4. Any other value is ignored.
Symbolwxp_currency_formats[CODE][position]"Store setting"One of left (€99), right (99€), left_space (€ 99), right_space (99 €).
Thousandswxp_currency_formats[CODE][thousand]"Store setting"Comma (1,234), Dot (1.234), Space (1 234), Apostrophe (1'234) or None (1234).
Decimal pointwxp_currency_formats[CODE][decimal]"Store setting"Dot (1.50) or Comma (1,50).

The thousands separator and the decimal point must differ when the currency has more than 0 decimals. If a save would make them equal the format of that currency is not saved and the screen says "The number format of EUR was not saved: the thousands and decimal separators must differ." Saving a row with every format field on its default removes the stored override for that currency. All formats are stored in the one option wxp_currency_formats.

Currency Rate screen: price mode, coupons and fees

Setting (label)KeyDefaultWhat it does
Product prices: Use the price entered on each translated product / Convert the default-language price with the rates abovewxp_poly_price_mode (field wxp_poly_price_mode)ownown or convert. Any other stored value counts as own.
Fixed coupons: Convert with the rate / Use the amounts as enteredwxp_convert_couponsyesyes converts fixed cart and fixed product coupon amounts and the minimum and maximum spend of all coupons. no switches it off. Any stored value other than no counts as yes.
Fees added by other plugins: Use the amounts as entered / Convert with the ratewxp_convert_feesnoyes multiplies every cart fee by the rate. Any stored value other than yes counts as no.

A setting that is not part of the posted form is left as it is.

Product edit screen

Setting (label)KeyDefaultWhat it does
Use Product PriceProduct meta _wxp_poly_typeUnticked (no meta)Simple products only. When ticked, the product keeps its own price in Convert mode. Saved as yes; unticking removes the meta.

Other stored values

KeyWritten byWhat it holds
wxp_rates_baseEvery rate saveThe store currency code at the time the rates were saved. Used for the "store currency changed" warning.
wxp_currency_rateRate saves and the upgrade from 2.1The old per-language format: Polylang language term id → rate (1 for a language on the store currency, empty when no rate). Kept for downgrades; the plugin reads rates from wxp_currency_rates.

7. Developer reference

The plugin has no shortcodes, blocks, REST routes, AJAX or admin-post endpoints, WP-CLI commands, cron events, database tables, custom capabilities, roles or template overrides. The Currency Rate form posts to its own admin page and is handled on admin_init (priority 20). The licence client adds the screen Plugins → WpExperts Hub Licences (page slug wpexperts-hub-licences).

Filters and actions the plugin fires

NameParametersWhen and what for
wxp_poly_convert_shipping_rate (filter)bool $convert = true, WC_Shipping_Rate $rate, array $packageFor each shipping rate before it is multiplied by the language rate. Return false to leave that rate (cost and taxes) as it is.
wxp_poly_convert_fee (filter)bool $convert = true, stdClass $fee, WC_Cart $cartFor each cart fee, only when fee conversion is switched on. Return false to keep one fee unconverted.
wxp_poly_keep_cart_untranslated (filter)bool $keep = trueEvaluated each time Polylang for WooCommerce asks whether to translate the cart. The plugin returns false to Polylang for WooCommerce while this is true. Return false to let Polylang for WooCommerce translate the cart.
wxp_poly_rates_saved (action)array $rates (currency code → rate string)After exchange rates were saved from the Currency Rate screen or the language form.
wpxh_licence_server (filter)string $serverLicence client: base address of the licence server (default https://wpexpertshub.com). The WPXH_LICENCE_SERVER constant sets the default.
wpxh_licence_sslverify (filter)bool $verifyLicence client: whether to verify the server's certificate. It is true unless the server host looks like a local development host (.local, .test, .localhost, localhost, 127.0.0.1).

Examples

Leave one shipping method unconverted because it already quotes in the cart currency:

add_filter( 'wxp_poly_convert_shipping_rate', function ( $convert, $rate, $package ) {
    if ( 'my_live_carrier' === $rate->get_method_id() ) {
        return false;
    }
    return $convert;
}, 10, 3 );

With fee conversion switched on, keep one named fee as entered:

add_filter( 'wxp_poly_convert_fee', function ( $convert, $fee, $cart ) {
    return 'Handling fee' === $fee->name ? false : $convert;
}, 10, 3 );

Let Polylang for WooCommerce translate the cart again:

add_filter( 'wxp_poly_keep_cart_untranslated', '__return_false' );

Run code after rates change:

add_action( 'wxp_poly_rates_saved', function ( $rates ) {
    error_log( 'Rates saved: ' . wp_json_encode( $rates ) );
} );

Read data from the plugin object (an internal helper, not a stable API; available after plugins_loaded priority 20 when Polylang and WooCommerce are active):

$mc = isset( $GLOBALS['wxp_polylang_multi_currency'] ) ? $GLOBALS['wxp_polylang_multi_currency'] : null;
if ( $mc ) {
    $code = $mc->get_lang_effective_currency( 'fr' ); // currency of the language, or the store currency
    $rate = $mc->get_currency_rate( $code );          // 0.0 when no valid rate is saved
}

Hooks the plugin attaches to

Where the plugin replaces the value that WooCommerce or another plugin produced, any callback at an earlier priority is ignored.

HookPriorityWhat the plugin does
woocommerce_currency999Returns the language's currency when it has one (replaces earlier results).
woocommerce_get_price_html999Re-renders a product's price in the currency of the product's language when it differs from the one in force.
woocommerce_product_get_price, woocommerce_product_get_regular_price, woocommerce_product_get_sale_price999Convert mode only: returns the converted price (replaces the stored one).
woocommerce_related_products20Keeps only products of the same language.
rest_post_dispatch20Relabels Store API product prices.
wc_get_price_decimals, wc_get_price_thousand_separator, wc_get_price_decimal_separator, woocommerce_price_format, option_woocommerce_currency_pos, wc_price_args20Number format per currency.
pllwc_copy_post_metas, pll_sync_post_fields999Removes price fields from translation synchronisation.
pllwc_enable_cart_translation20Switches off Polylang for WooCommerce cart translation (see wxp_poly_keep_cart_untranslated).
woocommerce_package_rates100Converts shipping cost and tax.
woocommerce_shipping_free_shipping_is_available999Recomputes free-shipping availability with the converted minimum when the rate is not 1 (replaces earlier results then).
woocommerce_coupon_get_amount, woocommerce_coupon_get_minimum_amount, woocommerce_coupon_get_maximum_amount20Convert coupon values (switchable).
woocommerce_cart_calculate_fees (action)9999Converts fees when switched on.
woocommerce_add_cart_item10Stores poly_lang on the cart item.
woocommerce_add_to_cart_validation10Refuses a product priced in another currency.
template_redirect999Redirects to the cart's language.
woocommerce_cart_loaded_from_session5Repairs a cart with mixed currencies. The same hook, woocommerce_add_to_cart, woocommerce_cart_item_removed, woocommerce_cart_emptied and woocommerce_after_calculate_totals also run the cart-language refresh at priority 1.
woocommerce_cart_subtotal, woocommerce_cart_contents_total, woocommerce_cart_total, woocommerce_cart_product_price, woocommerce_cart_product_subtotal999Keep cart amounts in the cart's currency on pages of other languages.
woocommerce_get_shop_page_id, woocommerce_get_cart_page_id, woocommerce_get_checkout_page_id, woocommerce_get_myaccount_page_id, woocommerce_get_terms_page_id, woocommerce_get_shop_page_permalink20Translated WooCommerce pages (only without Polylang for WooCommerce).
woocommerce_checkout_create_order, woocommerce_store_api_checkout_update_order_meta10Saves _wxp_poly_lang on the order.
woocommerce_checkout_order_processed, woocommerce_store_api_checkout_order_processed5Copies the order language to Polylang for WooCommerce.
woocommerce_get_checkout_order_received_url20Thank-you page in the order's language (only without Polylang for WooCommerce).
product_type_options, woocommerce_admin_process_product_object10The Use Product Price checkbox and its saving.
before_woocommerce_init10Declares compatibility with custom_order_tables and cart_checkout_blocks, and declares incompatibility with product_block_editor.
plugins_loaded20Starts the plugin.
plugin_action_links_{basename}10Adds the Currency Rate and licence links.
init10Registers the plugin with the licence client (WPXH_Licence_Client_V2::register()), which then adds the hooks below.
Licence client: admin_menu, admin_init, admin_notices, pre_set_site_transient_update_plugins, http_request_args, http_request_host_is_external, upgrader_process_complete, in_plugin_update_message-{basename}10The licence screen under Plugins, handling of its forms, the reminder on the Plugins screen, adding update information to the WordPress update list, relaxed certificate checks for a local development licence server only, clearing the update cache after an update, and the message under an update that has no package.
Licence client: plugins_api20Supplies the "View details" window for this plugin's slug only; other plugins' results are returned unchanged.
admin_menu, admin_init (20), admin_enqueue_scripts, admin_notices10The Currency Rate screen, its saving, its script and style (only on the Currency Rate screen and the Polylang languages screen), and the warnings.

Options, meta, session and transients

NameKindContents
wxp_currency_ratesOption (not autoloaded)Array of currency code → rate string.
wxp_rates_baseOption (not autoloaded)Store currency when the rates were saved.
wxp_currency_rateOption (not autoloaded)Legacy per-language rates (2.1 format).
wxp_currency_formatsOption (not autoloaded)Array of currency code → decimals, position, thousand, decimal (only valid keys are kept).
wxp_poly_price_modeOption (not autoloaded)own or convert.
wxp_convert_couponsOption (not autoloaded)yes or no.
wxp_convert_feesOption (not autoloaded)yes or no.
_wxp_poly_currencyTerm meta on Polylang language termsCurrency code of the language, upper case.
_wxp_poly_typeProduct metayes when Use Product Price is ticked.
_wxp_poly_langOrder metaSlug of the language the order was placed in.
poly_langCart item dataLanguage slug of the cart item.
wxp_poly_cart_langWooCommerce session valueLanguage slug of the cart.
wxp_poly_notice_{user id}Transient, 120 secondsError messages to show after saving the Currency Rate screen.
_wxp-polylang-multi-currency_licence_key, _wxp-polylang-multi-currency_key_statusOptions (licence client)The licence key and its status (active or inactive).
wpxh_licence_check_v2Site transient (licence client)Update information for all WpExperts Hub plugins, cached 12 hours.
wpxh_licence_msg_{user id}Transient, 120 seconds (licence client)The result message shown on the licence screen after an action.

The plugin defines the constant WXP_POLY_CURRENCY_VERSION (2.3.0). Text domain: wxp-polylang-multi-currency; the plugin ships a translation template (languages/wxp-polylang-multi-currency.pot) and no translations.

File layout

wxp-polylang-multi-currency/
  wxp-polylang-multi-currency.php        main class Wxp_Polylang_Multi_Currency (all front-end behaviour)
  classes/class-wxp-poly-currency-admin.php   Currency Rate screen, language form fields, notices, saving
  includes/dashboard.php                 markup of the Currency Rate screen
  assets/js/admin.js, assets/css/admin.css    live preview, language form script, styles
  licence/class-wpxh-licence-client.php  WpExperts Hub licence and update client (2.0.0)
  languages/wxp-polylang-multi-currency.pot
  uninstall.php                          removes the plugin's data on delete
  readme.txt

8. Privacy and data

What the plugin stores

  • Settings (options wxp_currency_rates, wxp_rates_base, wxp_currency_rate, wxp_currency_formats, wxp_poly_price_mode, wxp_convert_coupons, wxp_convert_fees), the currency of each language (term meta _wxp_poly_currency) and the Use Product Price flag (product meta _wxp_poly_type). None of this is personal data.
  • On each order: the language slug in the order meta _wxp_poly_lang. It is not personal data on its own.
  • In the cart: the language of each cart item and of the cart, in the WooCommerce session (wxp_poly_cart_lang) and in the cart item data (poly_lang). WooCommerce keeps cart item data with the cart session and, for logged-in customers, in their saved cart (user meta).
  • Short-lived: the transient wxp_poly_notice_{user id} (120 seconds) holds validation messages for the administrator who just saved the Currency Rate screen.

Cookies

The plugin sets no cookies of its own. It uses the session that WooCommerce already creates for carts.

Data sent to other servers

The only outbound requests come from the bundled licence client, to https://wpexpertshub.com/wp-json/wphub-licence/v1/ (JSON over HTTPS, timeout 20 seconds). Like every WordPress web request, they carry WordPress's standard User-Agent header, which contains your WordPress version and site address. No customer, order, cart or product data is sent.

RequestWhenWhat is sent
activateYou click Activate licenceThe plugin slug (wxp-polylang-multi-currency), the licence key you typed and your site address (site_url()).
deactivateYou click Deactivate licenceThe slug, the saved licence key and the site address.
send-keyYou click Send licence keyThe slug and the email address you typed.
checkWhen WordPress refreshes its plugin update information (and when the "View details" window of the plugin is opened), at most every 12 hours for the same data; one hour after a failed requestThe site address and, for every WpExperts Hub plugin registered on the site, its slug, its saved licence key (empty when none) and its installed version. This request is also made without a licence.

Retention, deactivation and uninstall

  • Deactivation deletes nothing.
  • Deleting the plugin (uninstall.php) removes the options wxp_currency_rate, wxp_currency_rates, wxp_rates_base, wxp_currency_formats, wxp_poly_price_mode, wxp_convert_coupons and wxp_convert_fees, the term meta _wxp_poly_currency, the product meta _wxp_poly_type and the wxp_poly_notice_* transients. On a multisite network it does this for every site.
  • Kept on purpose: the language stored on each order (_wxp_poly_lang), because it is part of the order history, and the licence data (_wxp-polylang-multi-currency_licence_key, _wxp-polylang-multi-currency_key_status and the licence transients).
  • The plugin adds no privacy policy text and has no personal data exporter or eraser, because it stores no personal data.

9. Troubleshooting

ProblemLikely cause and fix
The plugin does nothing and shows "WXP Multi Currency for Polylang and WooCommerce needs Polylang (or Polylang Pro) and WooCommerce to be active."Polylang or WooCommerce is not active. Activate both. The licence screen is not available until they are.
A language still shows the store currency.The language has no currency (Currency is on the store currency). Open Languages → Languages, click Edit on the language and choose a currency. Also check the page really belongs to that language.
Warning "EUR has no exchange rate yet, so shipping costs and fixed coupon amounts are not converted for it."No valid rate is saved for that currency. Type it on WooCommerce → Currency Rate or on the language form. Until then the rate counts as 1.
Warning "The store currency is now ..., but the exchange rates were entered when it was ...".You changed the store currency after entering the rates. Rates are always counted from the store currency, so every rate is wrong now. Re-enter every rate and save the Currency Rate screen.
"The exchange rate for EUR must be a number greater than zero."The value is not a number, is zero or negative, or contains a thousands separator. Use a dot or a comma as the decimal point and nothing else. The previous rate is kept.
"The number format of EUR was not saved: the thousands and decimal separators must differ."Choose different characters for the two separators, or set decimals to 0.
"Empty your cart before you add item in different currency."The cart already holds products priced in another currency than the product just added. Empty the cart or finish the order first. Languages that share a currency do not trigger this message.
Shop, cart or checkout keeps sending the shopper to another language.That is the cart lock: the cart holds products of another language's currency. Empty the cart to shop in the other language.
"Some products were removed from your cart because they are priced in a different currency."A saved cart with products of two currencies was loaded (for example after login). The plugin kept the products of one currency. Ask the customer to add the others again.
A translated product shows no price, or the wrong price.In the default mode each translation needs its own price in its language's currency. In Convert mode, check the product is simple, has a default-language translation, and does not have Use Product Price ticked. A currency without a rate converts with a factor of 1.
The Use Product Price checkbox is missing.It exists only for simple products in the classic product editor. It is not shown in WooCommerce's block-based product editor, and it has an effect only in Convert mode.
Shipping is converted twice, or a live carrier quote is too high.The shipping method already quotes in the cart currency. Return false from wxp_poly_convert_shipping_rate for it (see Examples).
A fee is too large in a foreign currency.Fee conversion is on and the fee is a percentage or already in the cart currency. Set Fees added by other plugins to Use the amounts as entered, or return false from wxp_poly_convert_fee for that fee.
Free shipping appears too early or too late in a foreign currency.The Free shipping minimum is typed in the store currency and multiplied by the rate, then compared with the cart subtotal. Check the rate and the free shipping method's "Apply minimum order rule before coupon discount" setting.
The plugin cannot be updated and the Plugins screen says "Automatic update is unavailable for this plugin."No active licence. Activate it under Plugins → WpExperts Hub Licences. If that fails, send the key request from "Can't find your key? Email it to me" or contact support.
A headless client or the block cart shows the wrong currency for a product.The plugin relabels the Store API product routes (/wc/store/v1/products) only. Other routes use the currency of the request.

10. FAQ

Do two languages with the same currency need two rates?

No. Rates and number formats belong to the currency: French, German and Spanish priced in EUR share one rate, and they can share a cart.

Do I need Polylang for WooCommerce?

No. Make Products translatable in Polylang's settings and translate the Shop, Cart, Checkout and My account pages; the plugin then uses those translated pages. Polylang for WooCommerce is also supported. In both cases prices are kept separate for each translation.

Are prices converted automatically?

Not by default. Each translation uses the price you typed. On WooCommerce → Currency Rate you can switch to converting the default-language price of simple products with your rates. Tick Use Product Price on a product to keep its own price.

Are coupons converted?

Yes, by default. The amount of a fixed cart or fixed product coupon and the minimum and maximum spend of any coupon are typed in your store currency and converted with the rate of the language's currency. Percentage coupons are never changed. You can switch this off on the Currency Rate screen.

Are fees converted?

Not by default. A fee another plugin adds is often a percentage of the cart, which is already in the cart currency. If your fees are fixed amounts typed in the store currency, switch on Convert with the rate under Fees on the Currency Rate screen. The wxp_poly_convert_fee filter can keep a single fee unconverted.

My shipping method already quotes in the cart currency.

Return false from the wxp_poly_convert_shipping_rate filter for that rate and it is left as it is.

Can a customer mix products from two currencies in one cart?

No. A cart holds one currency. Adding a product from another currency shows a message, and opening shop, cart or checkout pages in a language with another currency redirects to the language of the cart. Languages that share a currency can share a cart.

Why is Polylang for WooCommerce's automatic translation of the cart switched off?

That feature would replace the products of the cart with their translations in the language you browse, which could put a product priced in another currency into the cart. The plugin keeps one currency per cart instead. Return false from wxp_poly_keep_cart_untranslated to leave Polylang for WooCommerce in charge of the cart.

Does it work with the Cart and Checkout blocks?

Yes. Block and classic checkout both create the order in the currency of the cart's language, convert shipping with your rate and save the order language. The plugin also relabels Store API product responses with the currency of each product's language.

What happens to orders that are already placed?

Nothing. An order keeps the currency it was created with. Changing a rate or a language's currency affects new carts only.

Is it compatible with High-Performance Order Storage?

Yes. The plugin declares compatibility and stores the order language through the WooCommerce order object.

Do I need a licence for the plugin to work?

No. The plugin works without one. A licence key lets wpexpertshub.com deliver update packages through the normal WordPress updates screen. Activate it under Plugins → WpExperts Hub Licences.

Where do I get help?

Email support@wpexpertshub.com with your order number. See Support.

11. Changelog

2.3.0 - 2026-10-06

  • New: WooCommerce > Currency Rate redesigned. One row per currency (the languages that use it are listed), the exchange rate, the number format and a live preview of a converted amount; clear messages for missing or invalid rates; works on phones.
  • New: number format per currency: decimals, symbol position, thousands and decimal separators. Zero-decimal currencies (KRW, JPY, VND ...) and three-decimal currencies (KWD, BHD ...) get the right number of decimals automatically; a Korean order total is no longer a fraction of a won.
  • New: fixed coupon amounts and the minimum / maximum spend of coupons are converted with the language rate (switch on the Currency Rate screen). Optional conversion of fixed fees added by other plugins.
  • New: exchange rate field on Languages > Add / Edit language, currency list with names and symbols.
  • New: warnings when a currency has no exchange rate or when the store currency changed after the rates were entered.
  • New: filters wxp_poly_convert_shipping_rate, wxp_poly_convert_fee, wxp_poly_keep_cart_untranslated, action wxp_poly_rates_saved.
  • Fix: rates are now stored per currency. A language whose currency was changed no longer keeps the rate of its old currency, and two languages that share a currency can no longer hold two different rates. Rates saved by 2.1 are carried over automatically.
  • Fix: shipping costs of carts that already existed kept the old exchange rate after the rates were saved.
  • Fix: the header cart and other cart totals on a page of another language showed the amounts of the cart with the symbol of the page language (EUR amounts with a won sign).
  • Fix: two languages that share a currency (French/German in EUR) can now share a cart; before, switching between them emptied the cart for no reason.
  • Fix: a cart loaded with products of two currencies (a saved cart merged at login) is repaired instead of being priced in the wrong currency.
  • Fix: block checkout orders without a language in the request got the default language in Polylang for WooCommerce: wrong emails and the thank-you page in the wrong language.
  • Fix: related products on a product page listed products of the other languages, with their own currencies.
  • Fix: without Polylang for WooCommerce the shop link pointed to /fr/shop/ instead of the translated shop page.
  • Fix: Store API product responses carried the store currency (or the request language's) instead of the currency of the product's language.
  • Fix: free shipping minimum could miss the exact threshold by a floating point error; converted prices such as 17.99 x 1350 were rounded down by one.
  • Fix: the Currency Rate form is saved with a redirect, so reloading the page does not submit it again.
  • Added an uninstall routine that removes the plugin's settings (the language stored on orders is kept).

2.1.0 - 2026-09-27

  • Security - New licence client: licence data is exchanged as JSON only (the old client unserialized server responses) and a failed server request can no longer cause a fatal error.
  • Enhancement - One "Plugins → WpExperts Hub Licences" screen manages the licences of all WpExperts Hub plugins (previously only one plugin's licence page could be opened when several were installed). Existing activations are kept.
  • Enhancement - Updates are delivered through the standard WordPress update screen for sites with an active licence; "Email me my key" added.
  • Security: the Currency Rate form could be submitted by anyone (no nonce or capability check) and saved unsanitised values; it now requires the nonce and manage_woocommerce, and rates are validated.
  • Fix: block checkout and classic checkout created orders in the store currency (e.g. USD for a French/EUR cart) and did not convert shipping, because the Store API/wc-ajax requests carry no language. The cart language now sets the currency.
  • Fix: shipping taxes were not converted with the shipping cost.
  • Fix: the free shipping minimum could be multiplied several times (e.g. two languages using the same currency).
  • Fix: saving a language from code, the Polylang REST API or the wizard erased its currency and raised a PHP warning.
  • Fix: variable and on-sale product prices lost their price range/format and currency in the admin product list.
  • Fix: the "Use Product Price" option had no effect; price conversion from the default language is back as an opt-in setting.
  • New: cart/checkout/My account pages use their Polylang translations without Polylang for WooCommerce; shoppers are sent to the same page in the cart language; the order language is stored and the thank-you page uses it.
  • Declared compatibility with HPOS and the cart/checkout blocks; removed invalid feature declarations.
  • Escaping, sanitising and PHP 8.x fixes; tested with WordPress 7.1, WooCommerce 11.1 and Polylang 3.8.

1.8 - 17/07/2024

  • Added option to use price from base language or use different price for specific product.
  • Fixed free shipping based on cart total for different languages.

1.7 - 25/06/2024

  • Updates and Compatibility check to WC 9.0.2

1.6 - 30/08/2023

  • Compatibility check and fix.

1.0 - 10/03/2018

  • Initial Release.

12. Support

Email support@wpexpertshub.com. Include:

  • your order ID (the number on your purchase email);
  • the plugin version (2.3.0 or the version on your Plugins screen), and your WordPress, WooCommerce, Polylang and PHP versions;
  • whether Polylang for WooCommerce is installed and whether you use the classic or the block checkout;
  • a short description of what you expected and what happened, with the exact message shown, a screenshot of WooCommerce → Currency Rate and what you already tried.