Work and Experience
WordPress Engineering Client under NDA

Bilingual Checkout Funnel

An English sales funnel inside a French WooCommerce store, no multilingual plugin

3 pages
English funnel, no multilingual plugin
Client
Author selling a book (anonymized)
Engagement
Scoped i18n engineering
Stack
WordPressWooCommerceWPFunnelsPHP i18n / gettextFluentSnippets
01

Context

A French-language WooCommerce site needed to sell a book to an English-speaking audience through a dedicated funnel, landing, checkout and confirmation fully in English, without translating the rest of the store and without bolting a site-wide multilingual plugin onto a live production site for the sake of three pages. WPML-class machinery means duplicated content models, translation tables, locale switching and a permanent maintenance tax; for one product's purchase path, that is the wrong tool by an order of magnitude.

The engineered alternative: a self-contained translation layer that intercepts the PHP gettext filter chain for exactly three allowlisted funnel slugs, leaves the site locale untouched, survives WooCommerce's AJAX checkout re-rendering, and fails closed, the worst possible failure mode is a string rendering in French, never a broken storefront. Eleven tracked iterations took it to a hardened v1.1.0 running in production.

02

The Challenge

  • The translation had to cover strings from three sources at once, theme, WooCommerce core and the funnel plugin, each with its own text domain and timing.
  • WooCommerce re-renders checkout fragments over AJAX on every shipping, coupon or payment change, naive translations revert mid-checkout, exactly where trust matters most.
  • The French storefront is live revenue: the layer needed a provable inability to affect anything outside the funnel.
  • No theme edits allowed, the deliverable had to deploy as a self-contained snippet with instant rollback.
03

Scope and Approach

  1. 01

    Right-sized architecture

    Rejected the site-wide multilingual-plugin route, a full locale switch and translation tables are the wrong tool for one funnel on a live store, in favor of a self-contained translation layer intercepting the gettext filter chain, deployed as a versioned snippet with zero theme edits.

  2. 02

    Fail-closed scoping

    The layer activates on an allowlist of exactly three funnel slugs and nothing else, scope checked before any filter attaches. Off-funnel requests never even pay the filter overhead, and the failure mode is "renders in French", never "broken storefront".

  3. 03

    Filter-chain translation

    English applied through the PHP gettext filters across the theme, WooCommerce and funnel-plugin text domains on those pages, one product's entire purchase path coherent in English without touching the site locale or duplicating a single content object.

  4. 04

    AJAX-safe checkout

    Hooked the front-end checkout refresh cycle so translations re-apply after every WooCommerce fragment re-render, English survives shipping selection, coupon application and payment-method switches, the exact points where scoped translations usually silently revert.

  5. 05

    Hardened iteration

    Eleven tracked iterations to the semver-tagged v1.1.0 release, every version retained, edge cases found in real checkout flows fixed one at a time, with the client running a known-good build and an instant rollback path throughout.

04

Architecture flow

FR storefront
default locale
Slug allowlist
3 funnel pages
match
EN translation layer
no match
FR site untouched
Gettext filter chain
theme + WC + funnel domains
AJAX re-translation
checkout fragment refresh
Hardened release
v1.1.0, fail-closed
05

Technical Deep Dive

Why gettext filters instead of locale switching

Switching the locale per-request looks cleaner but drags the whole translation apparatus with it, .mo loading, plugin locale side effects, cache-key fragmentation. Intercepting the gettext filter chain translates exactly the strings that render on exactly the pages that matter, with the original French as the automatic fallback for anything unmapped, inherently fail-safe.

The checkout-fragment problem

WooCommerce's checkout is not a static page; it is re-rendered server-side and injected over AJAX on every state change. Server-side filters cover the fragments only if the scoping recognizes fragment requests as belonging to the funnel, and the front-end hook covers what renders client-side. Missing either half is why "translated checkouts" revert to the source language the moment a customer picks shipping.

Fail-closed as a design proof

The allowlist is checked positively, the layer does nothing unless the current request provably belongs to the three funnel slugs. There is no code path in which the French storefront is modified, which is a property you can state to a nervous client as an architectural fact rather than a testing outcome.

What eleven iterations bought

The version history reads like a hardening log: strings surfacing from unexpected text domains, fragment requests needing scope recognition, edge cases found only by walking the real checkout with real payment methods. Each iteration closed one class of leak and shipped independently, by v1.1.0 the layer had been proven against the checkout as customers actually use it, not as the happy path imagines it.

06

Engineering Decisions

Three pages of i18n over a multilingual platform

The business needed one funnel in English, not a bilingual site. Matching the solution size to the problem saved the client the permanent WPML maintenance tax and kept the live store's risk surface at zero.

Snippet deployment with retained versions

Deployed as a self-contained versioned snippet, no theme coupling, no build step, rollback is "activate the previous version". Eleven iterations were cheap precisely because deployment was.

Translations in one reviewable map

The English strings live in a single translation mapping inside the snippet rather than scattered through template overrides, the author can read every customer-facing sentence in one place, request a wording change, and receive it as a one-line diff in the next version.

07

Results

  • An English purchase funnel live inside a French store, one product, three pages, zero multilingual plugin, zero site-locale changes.
  • Fail-closed by construction: outside the allowlisted funnel, the French site cannot be affected.
  • Translations survive WooCommerce checkout-fragment AJAX refreshes, the point where scoped i18n usually breaks.
  • In production and selling, hardened across 11 tracked iterations to v1.1.0 with instant rollback at every step.

Have a system that needs
this kind of work?

Tell me what is breaking or what you need built. I will reply with how I would approach it and what I would check first.

Chat on WhatsApp