Lessons 2 and 3 each send one prompt per post to wp_ai_client_prompt(), and that prompt is
self-contained, it knows nothing about how any other post was translated. Run either ability
against ten posts that all mention your product name or a recurring technical term, and there’s
no guarantee the model renders that term the same way twice. One draft might translate a brand
name that should stay untouched, another might use two different words for the same technical
concept. This lesson fixes that with a glossary fed into every translation prompt through
using_system_instruction(), the builder method Course 12 covered in depth as the highest-
leverage way to shape a model’s standing behavior.
Course 12’s lesson on using_system_instruction(), this lesson treats that method as already
understood and applies it to a specific problem. Either Lesson 2’s WPML ability or Lesson 3’s
Polylang ability, since this lesson modifies that generation step rather than building a new
ability from scratch.
Step 1: Why one good prompt per post isn’t enough
The translation prompt in Lessons 2 and 3 already asks the model to preserve meaning and tone,
and a capable model will generally do that well within a single call. What it can’t do on its own
is remember that a different post, translated an hour earlier in a separate request, rendered a
particular term a specific way. Every call to wp_ai_client_prompt() starts fresh. Left alone,
this produces small, inconsistent variations across a site’s translated content, exactly the kind
of thing a bilingual reader notices even when each individual sentence reads fine.
A stronger model doesn’t fix this by itself, since the issue isn’t translation quality within one call, it’s the absence of any shared state between calls. The fix has to be supplying that shared state yourself, a glossary, on every call.
Step 2: Store the glossary as structured data, not a paragraph of prose
A glossary works best as data your code can query and grow, not as a string manually re-edited by
hand. Store it as a wp_option, an array keyed by source term, each entry recording whether the
term should ever be translated at all and, if so, its exact rendering per target language.
// File: translation-glossary/translation-glossary.php
function translation_glossary_get() {
return get_option( 'translation_glossary', array(
'Acme Sync' => array(
'do_not_translate' => true, // A product name, never translate this string, in any language.
),
'webhook' => array(
'do_not_translate' => false,
'es' => 'webhook', // Commonly left untranslated in Spanish technical writing too.
'fr' => 'webhook',
),
'dashboard' => array(
'do_not_translate' => false,
'es' => 'panel de control',
'fr' => 'tableau de bord',
),
) );
}
function translation_glossary_build_instruction_block( $target_language ) {
$lines = array();
foreach ( translation_glossary_get() as $term => $rule ) {
if ( ! empty( $rule['do_not_translate'] ) ) {
$lines[] = "- Never translate \"{$term}\", keep it exactly as written in every language.";
continue;
}
if ( isset( $rule[ $target_language ] ) ) {
$lines[] = "- Always translate \"{$term}\" as \"{$rule[ $target_language ]}\" in this language, never a synonym.";
}
}
return implode( "\n", $lines );
}
Keeping the glossary in a wp_option means an editor can grow it over time (a new product name,
a newly recurring technical term) without touching any PHP, and every translation call from this
point forward reads the same, current list.
Step 3: Feed the glossary through using_system_instruction(), not the prompt text
The glossary is standing behavior, it applies to this translation and every other one, which is
exactly the distinction Course 12 draws between using_system_instruction() and with_text().
The post content being translated belongs in with_text(), the glossary and tone rules belong in
using_system_instruction().
// File: translation-glossary/translation-glossary.php
function translation_glossary_translate_with_terminology( $source, $target_language ) {
$glossary_block = translation_glossary_build_instruction_block( $target_language );
$system_instruction =
// Role
"You are translating WordPress content into the language identified by the code " .
"\"{$target_language}\", preserving meaning, tone, and paragraph structure. " .
// Constraints, including the glossary
"Follow these terminology rules exactly:\n{$glossary_block}\n" .
"Do not summarize or shorten the content. " .
// Output shape
'Return the translated title on the first line prefixed with "Title: ", then the ' .
'translated body after a blank line.';
$response = wp_ai_client_prompt()
->with_text( "Title: {$source->post_title}\nContent: {$source->post_content}" )
->using_system_instruction( $system_instruction )
->using_model_preference( 'anthropic/claude', 'openai/gpt', 'google/gemini' )
->generate_text();
return $response;
}
Notice the glossary rules live inside using_system_instruction() alongside the role and output
shape instructions, following the same role, constraints, output-shape ordering Course 12
recommends, while the actual post content stays in with_text(). Swap this function in for the
prompt-building step inside Lesson 2 or Lesson 3’s _create_translation() helper, everything else
in either ability, the duplicate guard, the wp_insert_post() call, the WPML or Polylang linking,
stays exactly the same.
Step 4: Keep the glossary short and specific
Test it
Translate two different posts that both mention the same glossary term, and confirm it renders identically in both:
wp-env run cli wp eval '
$post_a = get_post( 10 );
$post_b = get_post( 55 );
echo translation_glossary_translate_with_terminology( $post_a, "es" );
echo "\n---\n";
echo translation_glossary_translate_with_terminology( $post_b, "es" );
'
Read both outputs specifically for how each one renders the glossary term, not just for overall translation quality, that’s the specific thing this lesson’s change is meant to fix.
Recap
Terminology drift happens because each wp_ai_client_prompt() call starts with no memory of any
other call, and the fix is supplying that shared context yourself: a glossary stored as structured
data, built into a plain-language instruction block per target language, and passed into
using_system_instruction() alongside the role and output-shape rules, while the actual content
stays in with_text(). This is a direct application of Course 12’s system instruction pattern to
one specific, recurring translation problem, not a new mechanism of its own.
Resources & further reading
- System Prompts and Instructions in the PHP AI Client SDK, this track, Course 12
- PHP AI Client SDK repository, GitHub
- get_option() function reference, developer.wordpress.org