WooCommerce Email Translation Guide
Overview
Bablic translates your storefront, but WooCommerce builds order emails on your server, so customers get them in your store's default language. This guide sets up your WooCommerce customer emails so each shopper gets them in the language they used on your site.
Translations are prepared once, ahead of time, through the Bablic Translation API. When WooCommerce sends an email, the translations are already saved on your site, so emails aren't delayed and no API call is made at send time.
How it works
- Remember the shopper's language. When someone checks out on a translated version of your store, the order records their language.
- Collect your email text. You send yourself each customer email once, and the plugin records the text those emails use.
- Translate it. One click sends that text to Bablic and saves a translation for each of your languages.
- Send translated emails. When WooCommerce sends a customer email, the plugin swaps in the translations for that order's language.
What gets translated
| Content | Translated? |
|---|---|
| Built-in email wording, such as greetings, "Order summary" and totals labels | ✅ Yes |
| Subjects, headings and additional content you set in WooCommerce > Settings > Emails | ✅ Yes |
| Your email footer text | ✅ Yes |
| Payment method names | ✅ Yes |
| Product names (after you add them in Step 4) | ✅ Yes |
| Emails sent to you or your staff | ❌ No, they stay in your store language |
| Shipping method names | ❌ No |
| Payment instructions from payment settings, such as bank transfer details | ❌ No |
| Information the customer typed in, such as addresses and order notes | ❌ No |
| Emails designed with WooCommerce's block email editor | ❌ No |
Before you start
You'll need:
- A translation key for each language and an API token. Contact Bablic Support at support@bablic.com and ask for Translation API access for WooCommerce emails.
- Access to your site's files through SFTP or your host's file manager, so you can add a file and edit
wp-config.php. - A WordPress admin account that can manage WooCommerce.
- An email address you can check for test orders.
The plugin works with both the classic and block checkout and requires PHP 7.4 or later. We recommend setting it up on a staging copy of your site first.
Step 1: Install the plugin
- On your server, open the
wp-contentfolder. If there's no folder namedmu-pluginsinside it, create one. - In
wp-content/mu-plugins, create a file namedbablic-email-translations.php. - Copy the code below into the file and save it.
Files in mu-plugins ("must-use" plugins) load automatically, so there's nothing to activate. After you save the file, WooCommerce > Bablic emails appears in your WordPress admin menu.
<?php
/**
* Plugin Name: Bablic Email Translations for WooCommerce
* Description: Sends WooCommerce customer emails in the language the shopper used on your site, using translations from the Bablic Translation API.
* Version: 1.0.0
* Author: Bablic
* Requires PHP: 7.4
*
* Install: copy this file to wp-content/mu-plugins/bablic-email-translations.php
*
* Settings: add these lines to wp-config.php, above "That's all, stop editing!":
*
* define( 'BABLIC_TX_TOKEN', 'your-api-token' );
* define( 'BABLIC_TX_KEYS', array(
* 'fr' => 'XXXX-XXXX-XXXX-XXXX', // language code => translation key
* 'de' => 'YYYY-YYYY-YYYY-YYYY',
* ) );
*/
defined( 'ABSPATH' ) || exit;
if ( ! defined( 'BABLIC_TX_TOKEN' ) ) {
define( 'BABLIC_TX_TOKEN', '' );
}
if ( ! defined( 'BABLIC_TX_KEYS' ) ) {
define( 'BABLIC_TX_KEYS', array() );
}
if ( ! defined( 'BABLIC_TX_ENDPOINT' ) ) {
define( 'BABLIC_TX_ENDPOINT', 'https://edge.bablic.com/xapis/TX/' );
}
final class Bablic_Email_Translations {
const COOKIE = 'bablic_email_lang';
const META = '_bablic_email_lang';
const SOURCE = 'source';
const OPT_SOURCE = 'bablic_email_source';
const OPT_COLLECT = 'bablic_email_collect';
const OPT_MAP = 'bablic_email_tx_';
const OPT_STATUS = 'bablic_email_status';
const BATCH = 100;
private static $active = false;
private static $collecting = false;
private static $map = array();
private static $translated = array();
private static $collected = array();
public static function init() {
// 1. Remember the shopper's language.
add_action( 'wp_footer', array( __CLASS__, 'print_language_script' ) );
add_action( 'woocommerce_checkout_create_order', array( __CLASS__, 'save_order_language' ), 10, 1 );
add_action( 'woocommerce_store_api_checkout_update_order_from_request', array( __CLASS__, 'save_order_language' ), 10, 1 );
add_action( 'woocommerce_admin_order_data_after_billing_address', array( __CLASS__, 'show_order_language' ) );
// 2. Swap in translations while a customer email is built.
add_action( 'woocommerce_email', array( __CLASS__, 'register_email_hooks' ) );
add_filter( 'woocommerce_mail_content', array( __CLASS__, 'end_email_filter' ), 1 );
add_action( 'woocommerce_email_sent', array( __CLASS__, 'end_email' ), 1, 0 );
add_action( 'shutdown', array( __CLASS__, 'end_email' ), 1, 0 );
// 3. Admin screen: WooCommerce > Bablic emails.
add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ), 99 );
add_action( 'admin_post_bablic_email', array( __CLASS__, 'handle_admin_action' ) );
}
/* ---------------------------------------------------------------------
* Languages
* ------------------------------------------------------------------ */
public static function keys() {
$keys = array();
foreach ( (array) BABLIC_TX_KEYS as $lang => $tkey ) {
$keys[ self::normalize( $lang ) ] = trim( (string) $tkey );
}
return $keys;
}
private static function normalize( $code ) {
return strtolower( str_replace( '_', '-', trim( (string) $code ) ) );
}
/** Returns the configured language for a code like "fr", "fr-FR" or "fr_fr", or "" if none. */
private static function match_language( $code ) {
$code = self::normalize( $code );
if ( '' === $code || self::SOURCE === $code ) {
return '';
}
$keys = self::keys();
if ( isset( $keys[ $code ] ) ) {
return $code;
}
$primary = strtok( $code, '-' );
return isset( $keys[ $primary ] ) ? $primary : '';
}
/** Bablic sets <html lang> on translated pages. Store it in a cookie so checkout can read it. */
public static function print_language_script() {
if ( is_admin() ) {
return;
}
?>
<script>
(function () {
function save() {
var lang = (document.documentElement.getAttribute('lang') || '').toLowerCase();
document.cookie = '<?php echo esc_js( self::COOKIE ); ?>=' + encodeURIComponent(lang) +
'; path=/; max-age=2592000; SameSite=Lax' + (location.protocol === 'https:' ? '; Secure' : '');
}
save();
if (window.MutationObserver) {
new MutationObserver(save).observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] });
}
})();
</script>
<?php
}
/** Returns the cookie language: a configured language, "source", or "" when there's no cookie. */
private static function cookie_language() {
if ( empty( $_COOKIE[ self::COOKIE ] ) ) {
return '';
}
$lang = self::match_language( sanitize_text_field( wp_unslash( $_COOKIE[ self::COOKIE ] ) ) );
return '' === $lang ? self::SOURCE : $lang;
}
public static function save_order_language( $order ) {
if ( ! $order instanceof WC_Order ) {
return;
}
$lang = self::cookie_language();
if ( '' === $lang ) {
return;
}
$order->update_meta_data( self::META, $lang );
if ( $order->get_customer_id() ) {
update_user_meta( $order->get_customer_id(), self::META, $lang );
}
}
public static function show_order_language( $order ) {
$lang = $order instanceof WC_Order ? (string) $order->get_meta( self::META ) : '';
if ( '' !== $lang ) {
printf(
'<p><strong>%s</strong> %s</p>',
esc_html( 'Email language:' ),
esc_html( self::SOURCE === $lang ? 'store language' : $lang )
);
}
}
private static function language_for( $object ) {
$lang = '';
if ( $object instanceof WC_Order ) {
$lang = (string) $object->get_meta( self::META );
if ( '' === $lang && $object->get_customer_id() ) {
$lang = (string) get_user_meta( $object->get_customer_id(), self::META, true );
}
} elseif ( $object instanceof WP_User ) {
$lang = (string) get_user_meta( $object->ID, self::META, true );
}
// New-account and password-reset emails are sent while the shopper is on the site.
if ( '' === $lang && ! is_admin() && ! wp_doing_cron() ) {
$lang = self::cookie_language();
}
return self::match_language( $lang );
}
/* ---------------------------------------------------------------------
* Email translation
* ------------------------------------------------------------------ */
public static function register_email_hooks( $mailer ) {
if ( ! is_object( $mailer ) || ! method_exists( $mailer, 'get_emails' ) ) {
return;
}
foreach ( $mailer->get_emails() as $email ) {
if ( $email instanceof WC_Email && $email->is_customer_email() ) {
add_filter( 'woocommerce_email_recipient_' . $email->id, array( __CLASS__, 'start_email' ), 1, 3 );
}
}
}
/** Runs when WooCommerce looks up the recipient, before it builds the subject and body. */
public static function start_email( $recipient, $object = null, $email = null ) {
self::end_email();
if ( ! $email instanceof WC_Email || ! ( $object instanceof WC_Order || $object instanceof WP_User ) ) {
return $recipient;
}
$lang = self::language_for( $object );
$map = '' !== $lang ? get_option( self::OPT_MAP . $lang, array() ) : array();
$map = is_array( $map ) ? $map : array();
self::$collecting = 'yes' === get_option( self::OPT_COLLECT );
if ( ! self::$collecting && empty( $map ) ) {
return $recipient;
}
self::$map = $map;
self::$translated = array_flip( array_map( 'strval', array_values( $map ) ) );
self::$active = true;
add_filter( 'gettext', array( __CLASS__, 'filter_text' ), 999, 1 );
add_filter( 'gettext_with_context', array( __CLASS__, 'filter_text' ), 999, 1 );
add_filter( 'ngettext', array( __CLASS__, 'filter_text' ), 999, 1 );
add_filter( 'woocommerce_email_get_option', array( __CLASS__, 'filter_email_option' ), 999, 4 );
add_filter( 'woocommerce_email_footer_text', array( __CLASS__, 'filter_text' ), 5, 1 );
add_filter( 'woocommerce_order_item_name', array( __CLASS__, 'filter_text' ), 999, 1 );
add_filter( 'woocommerce_get_order_item_totals', array( __CLASS__, 'filter_totals' ), 999, 1 );
return $recipient;
}
public static function end_email_filter( $content ) {
self::end_email();
return $content;
}
public static function end_email() {
if ( ! self::$active ) {
return;
}
remove_filter( 'gettext', array( __CLASS__, 'filter_text' ), 999 );
remove_filter( 'gettext_with_context', array( __CLASS__, 'filter_text' ), 999 );
remove_filter( 'ngettext', array( __CLASS__, 'filter_text' ), 999 );
remove_filter( 'woocommerce_email_get_option', array( __CLASS__, 'filter_email_option' ), 999 );
remove_filter( 'woocommerce_email_footer_text', array( __CLASS__, 'filter_text' ), 5 );
remove_filter( 'woocommerce_order_item_name', array( __CLASS__, 'filter_text' ), 999 );
remove_filter( 'woocommerce_get_order_item_totals', array( __CLASS__, 'filter_totals' ), 999 );
self::$active = false;
self::$map = array();
self::$translated = array();
if ( self::$collected ) {
self::add_source_strings( array_keys( self::$collected ) );
self::$collected = array();
}
}
public static function filter_text( $value ) {
if ( ! self::$active || ! is_string( $value ) || '' === trim( $value ) ) {
return $value;
}
if ( isset( self::$map[ $value ] ) ) {
$result = self::$map[ $value ];
} else {
$result = $value;
}
// Collect source text only: skip text that is already a translation, and format strings without words.
if ( self::$collecting && ! isset( self::$translated[ $value ] ) && preg_match( '/\p{L}{2,}/u', $value ) ) {
self::$collected[ $value ] = true;
}
return $result;
}
/** Subject, heading and additional content you entered in WooCommerce > Settings > Emails. */
public static function filter_email_option( $value, $email = null, $raw = null, $key = '' ) {
if ( is_string( $value ) && preg_match( '/^(subject|heading|additional_content)/', (string) $key ) ) {
return self::filter_text( $value );
}
return $value;
}
/** Totals labels and the payment method name. */
public static function filter_totals( $rows ) {
if ( ! self::$active || ! is_array( $rows ) ) {
return $rows;
}
foreach ( $rows as $key => $row ) {
if ( isset( $row['label'] ) ) {
$rows[ $key ]['label'] = self::filter_text( $row['label'] );
}
if ( 'payment_method' === $key && isset( $row['value'] ) ) {
$rows[ $key ]['value'] = self::filter_text( $row['value'] );
}
}
return $rows;
}
/* ---------------------------------------------------------------------
* Source text and translation
* ------------------------------------------------------------------ */
private static function source_strings() {
$source = get_option( self::OPT_SOURCE, array() );
return is_array( $source ) ? array_values( array_map( 'strval', $source ) ) : array();
}
private static function add_source_strings( array $strings ) {
$source = array_merge( self::source_strings(), array_map( 'strval', $strings ) );
update_option( self::OPT_SOURCE, array_values( array_unique( $source ) ), false );
}
private static function collect_product_names() {
$names = array();
$ids = array_merge(
wc_get_products( array( 'status' => 'publish', 'limit' => -1, 'return' => 'ids' ) ),
wc_get_products( array( 'status' => 'publish', 'limit' => -1, 'return' => 'ids', 'type' => 'variation' ) )
);
foreach ( $ids as $id ) {
$product = wc_get_product( $id );
if ( $product && '' !== trim( $product->get_name() ) ) {
$names[] = $product->get_name();
}
}
self::add_source_strings( $names );
return count( $names );
}
/** Placeholders like %s, %1$s, {site_title} and HTML tags must survive translation. */
private static function tokens( $text ) {
preg_match_all( '/%(?:\d+\$)?[sdfu]|\{[a-z0-9_]+\}|<\/?[a-z][a-z0-9]*/i', $text, $matches );
$tokens = array_map( 'strtolower', $matches[0] );
sort( $tokens );
return $tokens;
}
private static function translate_language( $tkey, array $source ) {
$map = array();
$skipped = 0;
foreach ( array_chunk( $source, self::BATCH ) as $batch ) {
$payload = array();
foreach ( $batch as $i => $text ) {
$payload[ 's' . $i . '_OneLinkTx' ] = $text;
}
$response = wp_remote_post(
BABLIC_TX_ENDPOINT . rawurlencode( $tkey ),
array(
'timeout' => 60,
'headers' => array(
'Content-Type' => 'application/json',
'X-TX_Token' => BABLIC_TX_TOKEN,
),
'body' => wp_json_encode( $payload ),
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$code = (int) wp_remote_retrieve_response_code( $response );
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( 200 !== $code || ! is_array( $data ) ) {
return new WP_Error( 'bablic_tx', sprintf( 'The Bablic Translation API returned HTTP %d.', $code ) );
}
if ( ! isset( $data['s0_OneLinkTx'] ) && isset( $data['tx'] ) && is_array( $data['tx'] ) ) {
$data = $data['tx'];
}
foreach ( $batch as $i => $text ) {
$key = 's' . $i . '_OneLinkTx';
if ( ! isset( $data[ $key ] ) || ! is_string( $data[ $key ] ) || '' === trim( $data[ $key ] )
|| self::tokens( $text ) !== self::tokens( $data[ $key ] ) ) {
$skipped++;
continue;
}
$map[ $text ] = $data[ $key ];
}
}
return array(
'map' => $map,
'skipped' => $skipped,
);
}
private static function translate_all() {
if ( function_exists( 'set_time_limit' ) ) {
@set_time_limit( 0 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
}
$source = self::source_strings();
$status = get_option( self::OPT_STATUS, array() );
$status = is_array( $status ) ? $status : array();
$messages = array();
foreach ( self::keys() as $lang => $tkey ) {
$result = self::translate_language( $tkey, $source );
if ( is_wp_error( $result ) ) {
$messages[] = $lang . ': ' . $result->get_error_message();
continue;
}
update_option( self::OPT_MAP . $lang, $result['map'], false );
$status[ $lang ] = array(
'translated' => count( $result['map'] ),
'skipped' => $result['skipped'],
'time' => time(),
);
$messages[] = sprintf( '%s: %d translated, %d skipped', $lang, count( $result['map'] ), $result['skipped'] );
}
update_option( self::OPT_STATUS, $status, false );
return implode( ' | ', $messages );
}
/* ---------------------------------------------------------------------
* Admin screen
* ------------------------------------------------------------------ */
public static function admin_menu() {
add_submenu_page( 'woocommerce', 'Bablic email translations', 'Bablic emails', 'manage_woocommerce', 'bablic-emails', array( __CLASS__, 'render_admin_page' ) );
}
private static function action_button( $op, $label, $primary = false ) {
?>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline-block;margin-right:8px;">
<input type="hidden" name="action" value="bablic_email">
<input type="hidden" name="op" value="<?php echo esc_attr( $op ); ?>">
<?php wp_nonce_field( 'bablic_email' ); ?>
<?php submit_button( $label, $primary ? 'primary' : 'secondary', 'submit', false ); ?>
</form>
<?php
}
public static function render_admin_page() {
$keys = self::keys();
$source = self::source_strings();
$status = get_option( self::OPT_STATUS, array() );
$collecting = 'yes' === get_option( self::OPT_COLLECT );
$notice = isset( $_GET['bablic_msg'] ) ? sanitize_text_field( wp_unslash( $_GET['bablic_msg'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
?>
<div class="wrap">
<h1>Bablic email translations</h1>
<?php if ( '' !== $notice ) : ?>
<div class="notice notice-info"><p><?php echo esc_html( $notice ); ?></p></div>
<?php endif; ?>
<?php if ( empty( $keys ) || '' === BABLIC_TX_TOKEN ) : ?>
<div class="notice notice-warning"><p>Add <code>BABLIC_TX_TOKEN</code> and <code>BABLIC_TX_KEYS</code> to wp-config.php to get started.</p></div>
<?php endif; ?>
<h2>Languages</h2>
<table class="widefat striped" style="max-width:800px">
<thead><tr><th>Language</th><th>Translation key</th><th>Translated</th><th>Skipped</th><th>Last translated</th></tr></thead>
<tbody>
<?php foreach ( $keys as $lang => $tkey ) : $s = isset( $status[ $lang ] ) ? $status[ $lang ] : array(); ?>
<tr>
<td><?php echo esc_html( $lang ); ?></td>
<td><code>…<?php echo esc_html( substr( $tkey, -4 ) ); ?></code></td>
<td><?php echo esc_html( isset( $s['translated'] ) ? $s['translated'] : '–' ); ?></td>
<td><?php echo esc_html( isset( $s['skipped'] ) ? $s['skipped'] : '–' ); ?></td>
<td><?php echo esc_html( isset( $s['time'] ) ? wp_date( 'Y-m-d H:i', $s['time'] ) : 'Never' ); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<h2>Email text</h2>
<p><?php echo esc_html( sprintf( '%d strings collected. Collecting is %s.', count( $source ), $collecting ? 'ON' : 'OFF' ) ); ?></p>
<p>
<?php self::action_button( $collecting ? 'collect_off' : 'collect_on', $collecting ? 'Stop collecting' : 'Start collecting', ! $collecting ); ?>
<?php self::action_button( 'products', 'Add product names' ); ?>
<?php self::action_button( 'translate', 'Translate now', $collecting ); ?>
<?php self::action_button( 'clear', 'Clear collected text' ); ?>
</p>
<?php if ( $source ) : ?>
<h2>Review</h2>
<table class="widefat striped">
<thead><tr><th>Source</th>
<?php foreach ( array_keys( $keys ) as $lang ) : ?><th><?php echo esc_html( $lang ); ?></th><?php endforeach; ?>
</tr></thead>
<tbody>
<?php
$maps = array();
foreach ( array_keys( $keys ) as $lang ) {
$maps[ $lang ] = (array) get_option( self::OPT_MAP . $lang, array() );
}
foreach ( array_slice( $source, 0, 1000 ) as $text ) :
?>
<tr><td><?php echo esc_html( $text ); ?></td>
<?php foreach ( $maps as $map ) : ?>
<td><?php echo isset( $map[ $text ] ) ? esc_html( $map[ $text ] ) : '<em>not translated</em>'; ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php
}
public static function handle_admin_action() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( 'Not allowed.' );
}
check_admin_referer( 'bablic_email' );
$op = isset( $_POST['op'] ) ? sanitize_key( wp_unslash( $_POST['op'] ) ) : '';
$msg = '';
switch ( $op ) {
case 'collect_on':
update_option( self::OPT_COLLECT, 'yes', false );
$msg = 'Collecting is on. Send each customer email once, then come back and click Stop collecting.';
break;
case 'collect_off':
update_option( self::OPT_COLLECT, 'no', false );
$msg = 'Collecting is off.';
break;
case 'products':
$msg = sprintf( 'Added %d product names.', self::collect_product_names() );
break;
case 'translate':
$msg = self::translate_all();
break;
case 'clear':
delete_option( self::OPT_SOURCE );
$msg = 'Collected text cleared. Existing translations are kept until you translate again.';
break;
}
wp_safe_redirect( add_query_arg( array( 'page' => 'bablic-emails', 'bablic_msg' => rawurlencode( $msg ) ), admin_url( 'admin.php' ) ) );
exit;
}
}
Bablic_Email_Translations::init();
Step 2: Add your keys
Open wp-config.php in your site's root folder and add these lines above the line that says /* That's all, stop editing! */ . Replace the token and keys with the ones Bablic Support sent you, adding one line for each language.
define( 'BABLIC_TX_TOKEN', 'your-api-token' );
define( 'BABLIC_TX_KEYS', array(
'fr' => 'XXXX-XXXX-XXXX-XXXX',
'de' => 'YYYY-YYYY-YYYY-YYYY',
) );
Each language code must match the language your translated site uses. A two-letter code such as fr covers every regional variant (fr-FR , fr-CA and so on). To give a region its own translations, add it separately, for example 'fr-ca' => '…' .
Tip: test with the demo key first. To check your setup before using real keys, use the demo token and translation key below for one language. The demo returns pseudo-translated text, with letters swapped for accented look-alikes, so you can see exactly what will be translated. Replace it with your real keys before going live.
define( 'BABLIC_TX_TOKEN', 'API-demo' ); define( 'BABLIC_TX_KEYS', array( 'fr' => '97BB-3CE8-1CD2-FE93' ) );
Step 3: Check that orders record the language
- Open your store in one of your translated languages and place a test order with your own email address.
- In WordPress, go to WooCommerce > Orders and open the order.
- Under the billing address, check for Email language. It should show the language code you set up, such as
fr.
Orders placed in your store's default language show Email language: store language. Orders placed before you installed the plugin don't have a language, so their emails stay in your store language.
If you don't see Email language, see Troubleshooting below.
Step 4: Collect your email text
- Go to WooCommerce > Bablic emails and click Start collecting.
- Send yourself each customer email your store uses, once. Use a test order with your own email address. The table below shows how to trigger each one.
- To translate product names, click Add product names. This adds the names of all published products and variations.
- Click Stop collecting.
| How to send it | |
|---|---|
| Processing order | Place a test order, or change a test order's status to Processing. |
| Completed order | Change the test order's status to Completed. |
| Order on-hold | Change the test order's status to On hold. |
| Refunded order | Refund the test order in full. |
| Order details / invoice | In the order, choose Send order details to customer from the Order actions menu and click Update. |
| Customer note | In the order, add a note and choose Note to customer. |
| New account | Create a customer account on your store. |
| Reset password | Click Lost your password? on the My Account page. |
If your store sends other customer emails, such as emails from subscription or booking plugins, send those too. You'll find the full list in WooCommerce > Settings > Emails.
Collecting only records text. It doesn't change the emails your customers receive.
Step 5: Translate
- On WooCommerce > Bablic emails, click Translate now.
- When it finishes, the Languages table shows how many strings were translated and skipped for each language.
- Check the Review table, which lists each piece of email text next to its translations.
A string is skipped when Bablic doesn't return a translation for it, or when the translation is missing a placeholder that WooCommerce fills in, such as {order_number} , %s or an HTML tag. Skipped strings stay in your store language so your emails never show broken placeholders. If many strings are skipped, contact Bablic Support.
Step 6: Test your emails
Place a new test order on a translated version of your store. The order confirmation should arrive in that language. Then change the order's status or add a customer note to check the other emails.
You're done. From now on, customer emails go out in each shopper's language automatically.
Keep translations up to date
Your translations are saved on your site, so they don't change on their own. Click Translate now again when you:
- Edit translations in Bablic
- Change email subjects, headings, additional content or footer text in WooCommerce
- Add products (click Add product names first)
- Add a language to
BABLIC_TX_KEYS
After a major WooCommerce update, or after installing a plugin that sends its own customer emails, repeat Step 4 so any new wording is collected, then translate again.
Troubleshooting
The order doesn't show an email language
- Make sure you placed the order on a translated version of your store, not the default language.
- The plugin reads the language from your translated pages and saves it in a cookie. Check that the shopper's browser accepts cookies, and that no caching or optimization plugin removes scripts from your site's footer.
- Check that the language code in
BABLIC_TX_KEYSmatches your translated site. If you're not sure which code your site uses, contact Bablic Support.
Emails still arrive in the store language
- Open the order and check that Email language shows the right language.
- On WooCommerce > Bablic emails, check that the language has translated strings. If it shows 0 or Never, click Translate now.
Some text in an email isn't translated
- Find the text in the Review table. If it's missing, start collecting, send that email again, stop collecting and click Translate now.
- If it shows not translated, it was skipped. See Step 5: Translate.
- Check What gets translated. Some content, such as shipping method names, isn't covered.
Translate now shows an error
| Error | What to do |
|---|---|
| HTTP 403 | Your API token is wrong. Check BABLIC_TX_TOKEN in wp-config.php . |
| HTTP 400 | Check that the translation key for that language is correct. |
| The page times out | Large catalogs can take a while to translate. Try again, or contact Bablic Support. |
Remove the plugin
Delete wp-content/mu-plugins/bablic-email-translations.php . Customer emails go back to your store language right away. Saved translations stay in your database as WordPress options whose names start with bablic_email_ , and you can delete them if you no longer need them.
Need help?
Contact Bablic Support at support@bablic.com.