PO vs MO in WordPress:
What Agencies Should Verify Before Handing a Locale to QA
The PO file is where humans argue about wording. The MO file is what WordPress loads at runtime. Agencies that confuse the two—or ship one without verifying the other—create QA loops where testers report ghosts: strings that were “translated” in Loco yet never compiled, or MO caches that lag behind a PO someone edited elsewhere.
Updated 2026
WordPress Localization Guide

If you run a WordPress agency, you have watched this failure mode more than once. A developer edits translations in a PO under version control, while another teammate saves different strings inside Loco on staging. QA opens the storefront, still sees stale English, and files a dozen tickets. The root cause is not “translation quality.” It is artifact discipline: which file is authoritative, when it was compiled, and whether the runtime binary the site loads actually reflects the text reviewers approved.
This article is a handoff checklist written for project leads and localization owners. It explains what PO and MO files are responsible for in a WordPress context, what to verify before you invite QA to spend hours clicking flows, and how AI-assisted translation inside Loco still has to obey the same compilation and caching realities as manual work.
When bulk or per-string assistance is part of your workflow, a Loco AI Auto Translator integration that keeps PO edits and compile-ready output aligned for WordPress gettext reduces the chance that speed upstream becomes confusion downstream.
PO and MO: two artifacts, one contract with the runtime
A PO file is plain text aimed at humans and diff tools. It lists msgid entries with their msgstr translations, plus comments that help translators understand context. An MO file is a binary message catalog compiled from that PO. WordPress does not “read” the PO on every request in the way a developer reads it in an editor. It loads the MO through gettext APIs, which expect a compiled structure optimized for lookup speed.
That distinction is why “we updated translations” is not a single fact. It is two events unless your toolchain guarantees compilation. Loco Translate can manage both the editable PO and the compiled MO in many setups, but your agency process must still state which environment is canonical and who triggers compilation. Otherwise QA receives contradictory instructions: “translations are done” refers to the PO, while the site still serves yesterday’s MO.
Ideal for review in Git, comments in tickets, and side-by-side comparison during legal sign-off. If your client asks for proof of what changed, the PO diff is the proof.
This is what production uses. If it is stale, customers see stale strings regardless of how polished the PO became in Loco five minutes ago.
The GNU gettext manual explains MO files as compiled message catalogs derived from PO sources. When a technical stakeholder pushes back on “why we need a compile step,” the gettext manual’s description of MO files is a neutral, engineer-friendly citation.
Another practical detail agencies should verbalize in kickoff: who is allowed to edit the PO in Loco on production versus staging. If production edits are permitted for hotfixes, your MO compile step must be part of the same change window, not “something we will batch on Friday.” Hotfix culture and gettext discipline can coexist, but only when compile and flush are explicit checklist items beside the deploy note. Otherwise you train the organization to believe the PO is truth while customers still read yesterday’s MO until an unrelated deploy accidentally refreshes caches.
How WordPress actually loads translations (and where agencies get surprised)
WordPress core and well-behaved plugins call translation APIs that ultimately rely on gettext loaders and file paths under wp-content/languages or plugin-specific language directories. The exact pathing depends on the component, text domain, and whether custom locations were registered. That means “we translated the plugin” is not interchangeable with “we translated core WooCommerce strings” unless you verified the text domains and target directories for each bundle.
Agencies also stumble over mixed delivery models. Some teams commit PO files to Git and deploy them with the theme. Others treat Loco’s saved copies as authoritative on the server. Both can work. The failure is hybrid ambiguity: two authorities, no merge strategy. Before QA, pick one story and document it in the runbook.
List each text domain in scope, the directory where its MO should live, and the username or automation account that last compiled it. QA should never guess.
For the broader expectations of how themes and plugins internationalize strings, WordPress developer documentation on internationalization remains the canonical overview to share with engineers who are new to gettext on WordPress.
Multisite and multi-store networks add another surprise: language files may be shared or overridden per site in ways that are invisible from a single Loco screen. A translator working on site ID 3 might compile an MO that site ID 4 never loads because a different upload path or symlink is in play. Network handoffs should name the site ID, URL, and whether language files live in a shared mu-plugins language directory or per-site uploads. QA tickets that omit those identifiers waste time comparing the wrong storefront to the right file.
The pre-QA verification grid: timestamps, hashes, and spot checks
Before you invite QA to execute a thirty-case spreadsheet, run a ten-minute verification grid. First, confirm the MO file’s modification time is newer than the PO you claim to have finished. Second, open a known string in Loco and compare it to what the storefront renders for the same locale. Third, repeat for one admin screen and one email preview if emails are in scope. If those three checks disagree, stop. QA should not be your first debugging layer for compilation problems.
Fourth, verify plural forms and ICU-like messages on at least one representative row per pattern type. Fifth, confirm that placeholder tokens survived compilation. These checks sound tedious; they are cheaper than a rejected release build.
Sixth, compare file sizes or checksums between environments. Staging MOs that mysteriously shrink often indicate an incomplete compile or a wrong text domain packaged into the binary. Seventh, if you use Git, ensure the MO you think you deployed is the same commit hash QA is testing. “Works on my machine” in localization usually means “works in my working tree,” which is not a release artifact.
Eighth, run one automated smoke script if you have it: load the cart page, the checkout block or shortcode page, and the order-received endpoint with a test product. Capture HTML snippets containing three known strings. Attach those snippets to the ticket. QA can then focus on linguistic quality instead of proving the pipeline functions.
| Check | Pass signal | Fail signal |
|---|---|---|
| MO newer than PO | Filesystem mtimes align with handoff note | MO older: compile step skipped |
| Storefront spot string | Rendered text matches approved PO row | English leak or stale phrasing |
| Cache layer | Purged or bypassed on staging | OPcache/page cache serves old PHP paths |
| Email preview string | Test send shows updated target text | Template still references old MO |
If AI assistance filled hundreds of rows, the verification grid becomes more important, not less. Models do not compile files; people and tooling do. Bulk AI translation in Loco with export-friendly PO workflows for WordPress locales still requires an owner who signs off that the MO on disk matches the approved PO state.
Caching: the invisible reason QA sees “wrong” language
Even when MO files are correct, caches lie. Full-page caches can store HTML from before a translation deploy. Object caches may memoize translation lookups. Opcode caches can delay seeing updated plugin language paths if deployment and symlink patterns are unconventional. Your handoff should name which caches were flushed, by whom, and when.
For WooCommerce flows, also remember customer session and geolocation edge cases. QA should test with a logged-in user in the target locale, not only an anonymous window that might follow different language negotiation rules. Document the test user, billing country, and profile language preference. Small details prevent false negatives.
CDN and edge layers deserve explicit mention. If HTML is cached at the edge, purging only WordPress object cache is insufficient. Your runbook should state whether translation deploys require a CDN purge, a cache tag invalidation, or a short TTL policy during QA week. Teams that forget this step spend days debating gettext when the issue is HTTP caching of rendered markup.
Cache flush checklist item with owner initials and timestamp.
“Hard refresh should fix it” as your only staging guidance.
Operational tooling should not pretend caching is someone else’s problem. Loco AI Auto Translator can help you finish gettext catalogs faster on multilingual WooCommerce stores, but your runbook still owns cache invalidation after MO updates.
Text domains, bundle boundaries, and why QA needs a map
A locale is not one file. It is a constellation of domains: woocommerce, your theme, your page builder companion plugin, the membership extension, the CRM bridge. QA tickets that say “German is broken” without naming the surface and domain waste engineering time. Your handoff map should correlate screens to domains and to the PO/MO pair that feeds them.
When AI assistance is used, require the same map. Bulk operations that span domains without explicit boundaries are how you accidentally “fix” a marketing plugin while leaving WooCommerce core leaks untouched, then argue about priorities.
Child themes deserve a special row on the map. Strings may exist in both parent and child domains; translators might fill one while the live site reads the other. Before QA, reconcile duplicates rather than hoping shoppers will not notice contradictory labels between shop archive and single product templates.
Artifacts that belong in every QA ticket packet
Attach artifacts, not vibes. Minimum: the PO export checksum or short hash, the MO path, a three-line summary of bundles touched, and screenshots of the spot checks. If legal reviewed payment strings, include the approval reference. If AI assistance was used, include the model or prompt version and the date range of the run. Future-you will thank present-you when a client asks what happened in March.
Version your handoff packet filename with the sprint number or release tag. “locale-handoff-de-DE-v3.zip” communicates more than “translations.zip.” Inside the archive, prefer text formats QA can open without special tools, plus a short README that explains how to reproduce your spot checks. The goal is to remove interpretive labor from the QA kickoff meeting.
Linking tooling to the packet is straightforward: Loco AI Auto Translator editor actions for per-string gettext review before MO compilation help you generate cleaner PO exports that match what you describe to QA.
When MO is right but UX still feels wrong: duplication and override chains
Sometimes QA is correct that the screen “looks untranslated” even when your MO is fresh. The culprit may be a duplicate string defined in another bundle with higher precedence, a child theme override, a hard-coded English fragment in a template bypassing gettext, or content stored in the database rather than in language files. Your verification grid should include one “deep trace” ticket per release that follows a single problematic label from UI to source.
This is not an argument against MO discipline. It is a reminder that gettext files are one layer in a stack. Agencies that document override chains reduce ping-pong between developers and testers.
When tracing failures, teach QA to capture the DOM snippet and the computed locale, not only a screenshot. Developers can grep templates faster when they see the exact English fragment and the URL. Likewise, teach developers to respond with the file path and gettext call they believe should own the string. That mutual discipline turns emotional debates into addressable tickets.
Git, deploy pipelines, and when the PO in the repo disagrees with Loco on the server
Many agencies keep PO files in Git for themes and custom plugins, then also use Loco on the server for vendor bundles. That split is workable if merges are intentional. It fails when a deploy overwrites a server-side PO with an older repo copy, or when Loco saves to a path Git never tracks. Your handoff should state the direction of truth for each bundle: repo wins, server wins, or explicit two-way sync with a named owner.
CI can compile MO artifacts in the pipeline and attach them as release binaries. That pattern is attractive because it removes “someone forgot to click compile” from the human path. It also requires developers to treat PO merges like code merges: conflicts must be resolved with translator input, not silently chosen by the last committer.
If you are not ready for CI compilation, at minimum block deploys that touch language directories without a paired MO update checklist item. Operations standards sound unrelated to translation quality until a Friday deploy reverts MO files and QA spends a weekend re-validating flows that were already green.
PO and MO alignment is the prerequisite for meaningful QA. Without it, you are testing phantoms. With it, testers can focus on genuine linguistic issues, accessibility of translated copy, and commerce flows that depend on precise wording near money movement. Treat every handoff as a small release: name the build, name the files, and name the caches you cleared. That formality feels heavy until it prevents a production incident that no amount of post facto “we thought it was deployed” narrative can unwind cleanly for the client.
If your team is investing in AI-assisted throughput inside Loco, invest equally in compile-and-handoff hygiene. The Loco AI Auto Translator WordPress plugin combining bulk gettext fills with Loco-native review workflows fits teams that want speed in the PO without sacrificing MO certainty at release time.


Finally a clear breakdown of PO vs MO! saved my team hours by attaching both files to tickets
The PO vs MO difference here is so real and honestly saved my team from a total QA disaster last sprint. We had two devs updating translations in different spots one in version control, the other in Loco on staging and no matter how many times we "fixed" the PO file, QA kept flagging old English strings. Turns out the MO file never got recompiled after our last edit. not blaming the tool at all, but this guide should've been the first thing we read before diving into localization.
Hey, this really cleared up the PO vs MO files confusion for me.