Wpml string translation
Skill Lonsdale201/wp-agent-skills/wpml/wpml-string-translation
A community-maintained collection of agent skills for WordPress plugin and theme development.
npx -y skills add Lonsdale201/wp-agent-skills --skill wpml-string-translationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Register and translate a WordPress plugin's dynamic strings with WPML — option values, admin-entered labels, and other free-form text that is NOT a static gettext string. Register via do_action('wpml_register_single_string', $domain, $name, $value) and read back via apply_filters('wpml_translate_single_string', $value, $domain, $name[, $lang]); legacy equivalents are icl_register_string( $context, $name, $value) and icl_t(). CRITICAL — these handlers live in the WPML String Translation add-on, NOT the base plugin — the base only FIRES the hooks, so without ST the translate filter returns the original (safe) and register is a silent no-op, while raw unguarded icl_register_string / icl_t calls FATAL. Covers detecting ST (defined('WPML_ST_VERSION'), class_exists('WPML_String_Translation'), function_exists('icl_register_string')), the function_exists fallback wrapper pattern, and when to use wpml-config admin-texts instead. Use for translatable settings, dynamic labels, or any registered string.
SKILL.md
9.9 KB, as published. Nobody here has run it
WPML: string registration & translation
For free-form / dynamic strings your plugin emits that WPML can't reach otherwise — option values entered in your settings, admin-defined labels, messages built at runtime. Static UI text stays on normal WordPress i18n (__(), _e() + .po/.mo); this skill is only for strings that live in the database or are generated dynamically.
Read this first — it needs the String Translation add-on
The base plugin only FIRES the string hooks; the handlers that actually store and translate strings ship with the separate WPML String Translation add-on. Verified: there is no add_action('wpml_register_single_string', …) or add_filter('wpml_translate_single_string', …) anywhere in sitepress-multilingual-cms, and icl_register_string / icl_t / icl_gettext are not defined in the base plugin.
Standard WordPress hook semantics then give you a safe default only for the hook form:
apply_filters('wpml_translate_single_string', $value, …)with no handler → returns$valueunchanged (original text; safe fallback).do_action('wpml_register_single_string', …)with no handler → silent no-op.icl_register_string()/icl_t()called directly with ST inactive → fatalundefined functionunless guarded.
So: prefer the hook form (degrades safely), and if you use the legacy icl_* functions, guard every call with function_exists().
Detecting String Translation
// Preferred:
if ( defined( 'WPML_ST_VERSION' ) ) { /* ST active */ }
// Base ships a helper that does exactly this:
if ( function_exists( 'wpml_is_st_loaded' ) && wpml_is_st_loaded() ) { /* ST active */ }
// Also used internally:
class_exists( 'WPML_String_Translation' );
// Feature-detect the legacy API before calling it:
function_exists( 'icl_register_string' );
wpml_is_st_loaded() returns defined('WPML_ST_VERSION') (inc/functions.php:922-924); class_exists('WPML_String_Translation') is used at inc/translation-management/translation-management.class.php:1742.
Register and translate — the hook form (recommended)
Register the string once (typically when you save the option that holds it), then translate it on output:
$domain = 'my-plugin'; // WPML "context" that groups your strings
$name = 'welcome_heading'; // stable, unique per string within the domain
$value = get_option( 'my_plugin_welcome' );
// 1) Register (no-op if ST inactive):
do_action( 'wpml_register_single_string', $domain, $name, $value );
// 2) Translate on output (returns $value unchanged if ST inactive or untranslated):
echo esc_html( apply_filters( 'wpml_translate_single_string', $value, $domain, $name ) );
Verified signatures in the base plugin's own call sites:
- Register (simple form)
do_action('wpml_register_single_string', $domain, $name, $value)— class-wpml-ls-settings-strings.php:81-86. - Translate
apply_filters('wpml_translate_single_string', $value, $domain, $name)(3-arg) — class-wpml-ls-settings-strings.php:108; 4-arg form with an explicit target languageapply_filters('wpml_translate_single_string', $value, $domain, $name, $language_code)— class-wpml-endpoints-support.php:72. Omit the 4th arg to let ST use the current language.
(There is also a heavier package form do_action('wpml_register_string', $value, $name, $package, $title, $type) used by page-builder integrations — for ordinary plugin strings use the _single_string form above.)
Legacy icl_* form — guard it
icl_register_string( $context, $name, $value ) and icl_t( $context, $name, $value ) are the older API (provided by ST). They fatal if ST is inactive, so wrap them — this is exactly the pattern WPML ships in its theme-integration example (docs/theme-integration/wpml-integration.php:130-142):
function my_plugin_register_string( $context, $name, $value ) {
if ( function_exists( 'icl_register_string' ) && trim( $value ) ) {
icl_register_string( $context, $name, $value );
}
}
function my_plugin_t( $context, $name, $original_value ) {
return function_exists( 'icl_t' )
? icl_t( $context, $name, $original_value )
: $original_value; // graceful fallback
}
The hook form above needs no such guard (the hooks simply don't fire without ST), which is why it's preferred for new code.
When to use <admin-texts> in wpml-config.xml instead
For option values stored in wp_options, the cleaner path is often the declarative <admin-texts> section in wpml-config.xml — WPML String Translation reads it and exposes those options for translation automatically, no do_action('wpml_register_single_string', …) needed:
<admin-texts>
<key name="my_plugin_options">
<key name="welcome_heading" />
</key>
</admin-texts>
Both paths require String Translation. Use <admin-texts> for settings that live in an option; use runtime registration for strings built dynamically (loops, per-item labels) that don't map to a fixed option key. See wpml-config for the <admin-texts> shape.
Critical rules
- String registration/translation requires WPML String Translation. Detect with
defined('WPML_ST_VERSION')and design so your plugin still works (untranslated) without it. - Prefer the hook form (
wpml_register_single_string/wpml_translate_single_string) — it degrades to a no-op / passthrough with ST absent. Rawicl_register_string/icl_tfatal unguarded. - Guard every legacy
icl_*call withfunction_exists(). $domain+$namemust be stable. Changing them orphans existing translations (WPML keys strings by context + name). Namespace the domain to your plugin.- Register before you translate. A string that's never registered has no translation to return — the filter returns the original.
- Don't register a static gettext string here. Use
__()/_e()+ a.potfor fixed UI text; reserve this for DB/dynamic values. - Escape on output as usual (
esc_html, etc.) — the translated string is untrusted output like any other.
Common mistakes
// WRONG — direct legacy call; fatal if String Translation isn't active
echo icl_t( 'my-plugin', 'welcome', $value );
// RIGHT — hook form, safe without ST
do_action( 'wpml_register_single_string', 'my-plugin', 'welcome', $value );
echo esc_html( apply_filters( 'wpml_translate_single_string', $value, 'my-plugin', 'welcome' ) );
// WRONG — assuming translation "works" on base WPML with no ST add-on
// (the filter returns the ORIGINAL string, silently — verify WPML_ST_VERSION if you depend on it)
// WRONG — unstable name breaks saved translations on every save
do_action( 'wpml_register_single_string', 'my-plugin', 'welcome_' . time(), $value );
// RIGHT — stable name
do_action( 'wpml_register_single_string', 'my-plugin', 'welcome_heading', $value );
Cross-references
wpml-config—<admin-texts>for translating options declaratively (also needs ST).wpml-overview— the base-vs-add-on split and when strings are even the right tool.wpml-language-api— resolving translated object IDs (a different concern from translating strings).
What this skill does NOT cover
- The String Translation admin UI / scanning — end-user workflow, not developer API.
.po/.mogettext translation of static strings — that's core WordPress i18n;wpml-config.xmlhas no<gettext-domains>section (seewpml-configreference).- The page-builder package form (
wpml_register_stringwith a$package) beyond a mention — that's for builder integrations. - WPML's own internal string storage schema — interact via the hooks.
References
- Base FIRES the hooks (handlers are in ST): register class-wpml-ls-settings-strings.php:81-86, translate :108; 4-arg translate with language class-wpml-endpoints-support.php:72.
- Graceful
function_existswrapper pattern: docs/theme-integration/wpml-integration.php:130-142. - ST detection helper
wpml_is_st_loaded(): inc/functions.php:922-924;class_exists('WPML_String_Translation'): inc/translation-management/translation-management.class.php:1742. - WPML coding API docs: https://wpml.org/documentation/support/wpml-coding-api/.
- Official documentation: https://wpml.org/documentation/support/translate-texts-in-admin-screens-with-wpml-string-translation/