WooCommerce Guide
Deep, performant integration with WooCommerce - translates products, categories, attributes, and emails. Syncs inventory across language variants. Supports per-language currencies.
How It Works
The WooCommerce addon activates automatically when WooCommerce is installed and active. No manual registration is needed.
What Gets Translated
| Content | Translated |
|---|---|
| Product title + content | Yes |
| Product short description (excerpt) | Yes |
Product purchase note (_purchase_note) | Yes |
External product button text (_button_text) | Yes |
Variation description (_variation_description) | Yes |
Product categories (product_cat) | Yes |
Product tags (product_tag) | Yes |
Product attributes (pa_*) | Yes (Dynamic) |
| Product attribute values | Yes |
| Product image alt text | Yes (Via Media Translation) |
| Product gallery alt text | Yes (Via Media Translation) |
What Stays Shared (Not Translated)
| Field | Why Shared |
|---|---|
| Stock / Stock Status | Same physical product |
| SKU | Unique identifier across all languages |
| Price / Sale Price | Kept in sync; use currency table for conversion |
| Weight / Dimensions | Physical product properties |
| Virtual / Downloadable | Product nature, not language-dependent |
| Total Sales counter | Aggregated across all languages |
Orders
shop_order is not a translatable post type. Orders are language-tagged using the _perflocale_language meta key (set when the order is placed) but are never duplicated per language. This ensures inventory and reporting remain accurate.
Settings
Located at PerfLocale → Settings → WooCommerce (tab only visible when WooCommerce is active).
Inventory Sync
Keep stock, SKU, and pricing identical across all language variants of a product. Fires on woocommerce_process_product_meta after each product save.
Order Email Language
Send order confirmation, processing, completed, and other status emails in the language the customer used when placing the order. The language is stored on the order as _perflocale_language meta.
Email Subject & Heading Translation
WooCommerce email subjects, headings, and additional content are registered as translatable strings when you click Scan for Strings or save WC email settings. They appear on the PerfLocale Strings page under domain woocommerce with contexts like email_subject_customer_processing_order.
When the source text changes (e.g., you edit the subject in WooCommerce → Settings → Emails), PerfLocale migrates the old translation to the new string with a Needs Update status so translators know to review it. The old translation text is preserved as a starting point.
Custom email types can be added via the perflocale/woocommerce/translatable_email_ids filter (see Hooks documentation).
Translating Email Body Templates
PerfLocale translates email subject and heading through the PerfLocale Strings system (your own translations, entered on the Strings page). The email body template is rendered by WooCommerce itself using standard __() / _e() calls. PerfLocale switches the WordPress locale before the body renders (via switch_to_locale() on woocommerce_email_header), so WooCommerce’s own translation files take over for the body text.
However, switch_to_locale() only has an effect if the corresponding locale’s .mo files are actually installed on the server. If they’re missing, WooCommerce falls back to English - the body will appear untranslated even though the subject and heading are correctly in the target language.
Install language packs from the WordPress admin via Settings → General → Site Language (WordPress core) and WooCommerce → Status → Tools, or via WP-CLI:
# Install WordPress core + all active plugins' language packs for German and French.
wp language core install de_DE fr_FR
wp language plugin install --all de_DE fr_FR
# For a specific plugin only:
wp language plugin install woocommerce de_DE fr_FR
How to verify on your site: wp language core list --status=installed should show every locale you target with PerfLocale. If a language is listed in PerfLocale but missing from that output, order emails in that language will render subjects correctly but bodies will appear in English.
This is also true for the Workflow Notifier emails sent to translators - the subject uses your template verbatim, but the rendered status labels (Assigned, In progress, Review, Approved) come from PerfLocale’s own text domain and will only appear translated when the PerfLocale .mo files for that locale are present.
Per-Language Currency
Display product prices converted to a language-specific currency. Configure the currency code and exchange rate per language. Prices are converted at display time - all transactions use WooCommerce's default base currency.
Automatic Exchange Rate Sync
Fetch live exchange rates from a configurable API provider on a scheduled interval. Supports Frankfurter (ECB), ExchangeRate-API, Open Exchange Rates, CurrencyFreaks, and Fixer.io. Custom providers can be added via the perflocale/woocommerce/exchange_rate_providers filter. See Exchange Rates documentation for full details.
Product Attributes
All registered WooCommerce attributes (pa_* taxonomies) are automatically discovered and registered as translatable. New attributes created in WooCommerce → Attributes are included without any configuration.
URLs
All WooCommerce pages include the language prefix automatically:
/de/shop/- product archive/de/cart/- cart/de/checkout/- checkout/de/my-account/- account/de/my-account/orders/- order history/de/my-account/downloads/- downloads
Developer Hooks
Filters
perflocale/woocommerce/synced_product_fields
Control which meta keys are synced across language variants.
add_filter( 'perflocale/woocommerce/synced_product_fields', function ( array $fields, int $product_id ): array {
// Add a custom field to the sync list.
$fields[] = '_my_custom_field';
// Or remove a field if you want per-language pricing.
$fields = array_diff( $fields, [ '_price', '_regular_price', '_sale_price' ] );
return $fields;
}, 10, 2 );
Parameters:
array $fields- Default list of meta keys.int $product_id- ID of the product being saved.
perflocale/woocommerce/skip_inventory_sync
Prevent inventory sync for a specific product.
add_filter( 'perflocale/woocommerce/skip_inventory_sync', function ( bool $skip, int $product_id ): bool {
// Skip sync for products in a specific category.
if ( has_term( 'digital', 'product_cat', $product_id ) ) {
return true;
}
return $skip;
}, 10, 2 );
perflocale/woocommerce/translate_string
Override translation for a WooCommerce gateway title, description, or shipping label.
add_filter( 'perflocale/woocommerce/translate_string', function ( ?string $translated, string $text, string $slug, string $context ): ?string {
if ( $slug === 'de' && $text === 'Direct bank transfer' ) {
return 'Direktüberweisung';
}
return $translated; // Return null to fall through to default lookup.
}, 10, 4 );
Parameters:
string|null $translated- Null (no override yet) or a translated string.string $text- Original text.string $slug- Current language slug.string $context- Gateway ID or empty string.
perflocale/woocommerce/email_translation_enabled
Disable email language switching for a specific order.
add_filter( 'perflocale/woocommerce/email_translation_enabled', function ( bool $enabled, int $order_id ): bool {
// Always send admin notifications in site default language.
return $enabled;
}, 10, 2 );
Actions
perflocale/woocommerce/inventory_synced
Fires after inventory sync completes successfully.
add_action( 'perflocale/woocommerce/inventory_synced', function ( int $product_id, array $synced_ids, array $fields ): void {
error_log( "Synced product #{$product_id} to: " . implode( ', ', $synced_ids ) );
}, 10, 3 );
Parameters:
int $product_id- Source product that was saved.int[] $synced_ids- Language variant IDs that received the update.string[] $fields- Meta keys that were synced.
Behavior Notes
- No double-selling: Inventory sync runs at priority 100 on
woocommerce_process_product_meta, after WooCommerce has saved all product data. - Recursion guard: InventorySync tracks in-progress syncs in a static array to prevent circular updates between language variants.
- Locale switching:
LanguageRouter::filter_locale()overrides the WordPress locale to match the detected language. All WooCommerce UI strings using__()are automatically served in the correct language. - Cache: WooCommerce product transients are cleared for each sibling after sync (
wc_delete_product_transients). - REST API: Inventory sync also fires on
woocommerce_rest_insert_product_objectso bulk edits via the REST API are covered.