Hooks Reference
Developer reference for all apply_filters and do_action hooks in PerfLocale.
Filters
Settings & Configuration
perflocale/translatable_post_types
Filter the list of translatable post types.
add_filter( 'perflocale/translatable_post_types', function ( array $post_types ): array {
$post_types[] = 'product'; // Add WooCommerce products.
return $post_types;
} );
Parameters: array $post_types - Array of post type slugs.
File: src/Settings.php
perflocale/translatable_taxonomies
Filter the list of translatable taxonomies.
add_filter( 'perflocale/translatable_taxonomies', function ( array $taxonomies ): array {
$taxonomies[] = 'product_cat';
return $taxonomies;
} );
Parameters: array $taxonomies - Array of taxonomy slugs.
File: src/Settings.php
perflocale/excluded_paths
Filter URL paths excluded from language prefix injection.
add_filter( 'perflocale/excluded_paths', function ( array $paths ): array {
$paths[] = '/my-api/'; // Custom REST endpoint.
$paths[] = '/sitemap.xml'; // Sitemap file.
return $paths;
} );
Parameters: array $paths - Array of path prefixes (e.g., '/wp-json/').
File: src/Settings.php
perflocale/cookie_lifetime
Filter the language cookie lifetime in days.
add_filter( 'perflocale/cookie_lifetime', function ( int $days ): int {
return 30; // 30 days instead of default 365.
} );
Parameters: int $days - Cookie lifetime in days.
File: src/Router/LanguageRouter.php
perflocale/active_languages
Filter the list of active languages returned by the router.
// Hide a language on specific pages.
add_filter( 'perflocale/active_languages', function ( array $languages ): array {
if ( is_page( 'internal-only' ) ) {
return array_filter( $languages, fn( $l ) => $l->slug !== 'de' );
}
return $languages;
} );
Parameters: array $languages - Array of language objects.
File: src/Router/LanguageRouter.php
perflocale/translatable_options
Filter the list of WordPress options that have per-language values.
Parameters: array $options - Array of option names.
File: src/Cache/CacheInvalidator.php
Translation Creation
perflocale/translation/post_data
Filter the new post data before a translation is created via wp_insert_post().
add_filter( 'perflocale/translation/post_data', function ( array $data, int $source_id, string $target_slug ): array {
$data['post_author'] = get_current_user_id(); // Set current user as author.
return $data;
}, 10, 3 );
Parameters:
array $data- Post data array (post_type,post_status,post_title, etc.).int $source_id- Source post ID.string $target_slug- Target language slug.
File: src/Translation/PostTranslationManager.php
perflocale/translation/excluded_meta_keys
Filter the meta keys excluded from being copied when creating a translation.
add_filter( 'perflocale/translation/excluded_meta_keys', function ( array $keys, int $source_id ): array {
$keys[] = '_my_private_meta'; // Don't copy this to translations.
return $keys;
}, 10, 2 );
Parameters:
array $keys- Meta key names to exclude.int $source_id- Source post ID.
File: src/Translation/PostTranslationManager.php
Content Sync
perflocale/sync_fields
Filter the fields synchronized across translations when a post is saved.
// Add custom meta keys to sync programmatically.
add_filter( 'perflocale/sync_fields', function ( array $fields, string $post_type ): array {
if ( $post_type === 'product' ) {
$fields[] = '_price';
$fields[] = '_sku';
$fields[] = '_stock_status';
}
return $fields;
}, 10, 2 );
Parameters:
array $fields- Field names (built-in keys likefeatured_image,menu_order, or custom meta keys).string $post_type- The post type being saved.
Built-in field keys: featured_image, menu_order, post_parent, post_date, post_author, comment_status, ping_status. Any other value is treated as a meta key and synced via get_post_meta() / update_post_meta().
File: src/Translation/ContentSync.php
URL Handling
perflocale/url/convert
Filter a URL after it has been converted to a target language.
add_filter( 'perflocale/url/convert', function ( string $url, string $target_slug, string $current_slug ): string {
// Custom URL transformation.
return $url;
}, 10, 3 );
Parameters:
string $url- The converted URL.string $target_slug- Target language slug.string $current_slug- Current language slug.
File: src/Router/UrlConverter.php
perflocale/redirect_default_to_prefix
When “Hide URL prefix for default language” is unchecked, the plugin 301-redirects bare URLs (e.g. / or /contact/) to the prefixed form (/en/, /en/contact/) to avoid duplicate content. Return false to disable that redirect and let both forms resolve.
add_filter( 'perflocale/redirect_default_to_prefix', '__return_false' );
Parameters: bool $enabled - default true.
File: src/Router/LanguageRouter.php
SEO
perflocale/seo/hreflang_tags
Filter the full hreflang tag array (including the x-default entry) right before it’s emitted in <head> and on the Link HTTP header. Use it to add, remove, reorder, or mutate entries.
// Remove the x-default tag entirely.
add_filter( 'perflocale/seo/hreflang_tags', function ( array $tags ): array {
return array_filter( $tags, fn( $t ) => $t['hreflang'] !== 'x-default' );
} );
// Add a regional alternate that PerfLocale doesn't manage itself.
add_filter( 'perflocale/seo/hreflang_tags', function ( array $tags ): array {
$tags[] = [ 'hreflang' => 'en-ca', 'href' => 'https://ca.example.com/' ];
return $tags;
} );
Parameters: array $tags - array of ['hreflang' => string, 'href' => string], computed per-request and cached.
File: src/Frontend/HreflangTags.php
perflocale/seo/x_default_url
Filter just the URL used for the x-default entry. Cleaner than walking the whole tag array when you only want to redirect search engines to a language-picker landing page or a specific regional variant. Return an empty string to suppress the x-default entry for the current request.
// Point x-default at a language-picker landing page.
add_filter( 'perflocale/seo/x_default_url', fn() => home_url( '/languages/' ) );
// Only override for the homepage; fall back to PerfLocale's default elsewhere.
add_filter( 'perflocale/seo/x_default_url', function ( string $url ): string {
return is_front_page() ? home_url( '/choose-region/' ) : $url;
} );
Parameters:
string $default_url- URL currently slated for x-default (PerfLocale’s computed value).object $default- default-language object (slug, locale, name, etc.).string $current_slug- slug of the language currently being rendered.
File: src/Frontend/HreflangTags.php
perflocale/seo/hreflang_include_fallbacks
Override the Include Fallback Languages setting programmatically. When the filter returns true, hreflang emits a tag for every active language - even those without an explicit translation of the current post - as long as the language URL would render 200 (i.e. missing_translation_action is show_default or the language has a non-empty fallback chain). Filter value wins over the admin toggle, so addon or theme code can force the inclusive mode without touching settings.
// Always advertise every active language, regardless of admin setting.
add_filter( 'perflocale/seo/hreflang_include_fallbacks', '__return_true' );
// Turn on only for singular posts with an SEO-friendly post status.
add_filter( 'perflocale/seo/hreflang_include_fallbacks', function ( bool $enabled ): bool {
if ( is_singular() && 'publish' === get_post_status() ) {
return true;
}
return $enabled;
} );
Parameters: bool $enabled - current setting value (defaults to false).
Since: v1.0.10
File: src/Frontend/HreflangTags.php
Modern SEO & UX (added in 1.2.0)
perflocale/content_language/value
Filter the BCP-47 locale emitted in the Content-Language HTTP response header. Default is the current language’s locale field normalised to hyphen-form (e.g. de_DE → de-DE); return an empty string to suppress the header on this request.
add_filter( 'perflocale/content_language/value', function ( string $locale, $current_lang ): string {
// Force the generic region-less code on specific templates.
if ( is_page( 'global-press' ) ) {
return 'en';
}
return $locale;
}, 10, 2 );
Parameters: string $locale, object $current_lang - the language object from the router.
File: src/Frontend/ContentLanguageHeader.php
perflocale/view_transitions/css
Filter the CSS block emitted for the cross-document View Transitions opt-in. The default block opts in to automatic navigation transitions with a 240ms crossfade AND includes a @media (prefers-reduced-motion: reduce) guard that zeros the animation duration for users with the OS-level reduced-motion preference (navigation still completes instantly - only the crossfade is suppressed). Returning '' disables output entirely. If you override the default, keep the reduced-motion guard in your replacement CSS to preserve WCAG 2.1 SC 2.3.3 compliance.
// Add named transitions so the header and main content morph across pages.
add_filter( 'perflocale/view_transitions/css', function ( string $css ): string {
return $css . 'header.site-header { view-transition-name: site-header; }
main { view-transition-name: main-content; }';
} );
Parameters: string $css - default CSS block (includes @view-transition, the 240ms crossfade rules, and the prefers-reduced-motion guard).
File: src/Frontend/ViewTransitionsEmitter.php
perflocale/view_transitions/should_emit
Short-circuit the View Transitions emitter on specific templates. Useful when a theme runs its own GSAP/slider animations on navigation that would fight the browser transition.
add_filter( 'perflocale/view_transitions/should_emit', function ( bool $should ): bool {
if ( is_singular( 'portfolio' ) ) {
return false; // GSAP page transitions are active on portfolio items.
}
return $should;
} );
Parameters: bool $should_emit - defaults to true when the setting is on.
File: src/Frontend/ViewTransitionsEmitter.php
perflocale/prerender/rules
Fallback path only (WP 6.4–6.7). Filter the Speculation Rules JSON payload before emission. On WP 6.8+ use Core’s wp_load_speculation_rules action instead; our rule is registered via that API and is no longer filterable through this hook.
add_filter( 'perflocale/prerender/rules', function ( array $rules, array $urls ): array {
// Switch to prefetch instead of prerender on this site.
if ( isset( $rules['prerender'] ) ) {
$rules = [ 'prefetch' => $rules['prerender'] ];
}
return $rules;
}, 10, 2 );
Parameters: array $rules, array $urls.
File: src/Frontend/SpeculationRulesEmitter.php
perflocale/prerender/should_emit
Short-circuit the Speculation Rules emitter on specific templates (applies to BOTH the WP 6.8+ Core API path and the WP < 6.8 fallback). Useful for pages where prerendering the switcher target would waste bandwidth - paginated archives with heavy media, or URLs that trigger non-idempotent GET side effects.
add_filter( 'perflocale/prerender/should_emit', function ( bool $should ): bool {
if ( is_page_template( 'templates/heavy-media-gallery.php' ) ) {
return false;
}
return $should;
} );
Parameters: bool $should_emit.
File: src/Frontend/SpeculationRulesEmitter.php
perflocale/prerender/use_core_api
On WP 6.8+ PerfLocale registers its prerender rule via Core’s native Speculation Rules API (wp_load_speculation_rules action), so our rule lands inside Core’s single output script. Return false from this filter to force the fallback self-emit path instead - a standalone <script type="speculationrules" id="perflocale-speculationrules"> emitted at wp_footer priority 20. Primarily useful for integrators who want the perflocale/prerender/rules filter (fallback-path only) to remain active on modern WP, or for test environments exercising the fallback code without downgrading WordPress.
add_filter( 'perflocale/prerender/use_core_api', '__return_false' );
The same path can be forced globally by defining the PERFLOCALE_FORCE_SR_FALLBACK constant - the filter is a per-request escape hatch, the constant is a site-wide switch.
Parameters: bool $use_core - defaults to true when \WP_Speculation_Rules exists.
File: src/Frontend/SpeculationRulesEmitter.php
perflocale/fallback/wrap_nosnippet
Filter whether to wrap singular-post content with <div data-nosnippet> when the visitor is viewing a non-default-language URL that’s showing the default-language post as fallback (missing_translation_action = show_default). Fires only when fallback is already detected; use this to exempt specific post types or IDs. The guard applies to both explicitly-linked default-language posts AND posts that have no translation-link entry (treated as default-language, matching the plugin-wide convention), so pages authored before PerfLocale was installed are protected too.
add_filter( 'perflocale/fallback/wrap_nosnippet', function ( bool $wrap, int $post_id ): bool {
$post = get_post( $post_id );
if ( $post && $post->post_type === 'product' ) {
return false; // Let product descriptions through - SKUs are language-agnostic.
}
return $wrap;
}, 10, 2 );
Parameters: bool $wrap (default true), int $post_id.
File: src/Translation/FallbackSnippetGuard.php
perflocale/fallback/nosnippet_tag
Customise the HTML tag used to wrap fallback content. Default is div; use span for themes that render the content in an inline context where a block-level wrapper breaks layout. Must match /^[a-z]{1,8}$/ - invalid values fall back to div.
add_filter( 'perflocale/fallback/nosnippet_tag', fn( $tag ) => 'span' );
Parameters: string $tag, int $post_id.
File: src/Translation/FallbackSnippetGuard.php
perflocale/indexnow/should_push
Veto an IndexNow push on a case-by-case basis. Use to skip preview/staging URLs, or to limit pushes to a rate-controlled window.
add_filter( 'perflocale/indexnow/should_push', function ( bool $should, array $urls, array $context ): bool {
// Stop all pushes outside business hours.
$hour = (int) wp_date( 'G' );
return $hour >= 9 && $hour <= 17 ? $should : false;
}, 10, 3 );
Parameters: bool $should, array $urls, array $context (may contain trigger_post_id).
File: src/Seo/IndexNowPusher.php
perflocale/indexnow/urls
Modify the URL list before it’s sent. Add or remove URLs on the fly - useful for integrations with other plugins that know about URLs PerfLocale doesn’t.
add_filter( 'perflocale/indexnow/urls', function ( array $urls ): array {
// Also ping the translated AMP versions.
$amp = array_map( fn( $u ) => trailingslashit( $u ) . 'amp/', $urls );
return array_merge( $urls, $amp );
} );
Parameters: array $urls, array $context.
File: src/Seo/IndexNowPusher.php
perflocale/indexnow/endpoint
Override the IndexNow endpoint per-host. Defaults to Cloudflare’s relay (https://api.indexnow.org/indexnow, which covers Google + Bing + Yandex) when the Cloudflare-relay setting is on, or direct Bing otherwise. You can force a different endpoint for specific hosts.
add_filter( 'perflocale/indexnow/endpoint', function ( string $endpoint, string $host ): string {
// Send .cn hosts to Yandex directly.
if ( str_ends_with( $host, '.cn' ) ) {
return 'https://yandex.com/indexnow';
}
return $endpoint;
}, 10, 2 );
Parameters: string $endpoint, string $host, array $urls.
File: src/Seo/IndexNowPusher.php
perflocale/indexnow/key
Override the auto-generated 32-character hex key. The key is served by the plugin at {host}/{key}.txt for search-engine ownership verification - if you supply your own, make sure it’s also accessible at that URL. Rarely needed.
add_filter( 'perflocale/indexnow/key', fn() => defined( 'MY_INDEXNOW_KEY' ) ? MY_INDEXNOW_KEY : '' );
Parameters: string $key.
File: src/Seo/IndexNowPusher.php
Core filter - also honoured on WP < 6.8
The WordPress Core filter wp_speculation_rules_href_exclude_paths is automatically respected by PerfLocale on WP 6.8+ (Core applies it). On older WP (6.4–6.7), the fallback path also fires this filter so exclusions like /cart/* or /checkout/* work consistently across versions.
// Exclude action URLs from speculative prerendering.
add_filter( 'wp_speculation_rules_href_exclude_paths', function ( array $paths, string $mode ): array {
if ( $mode === 'prerender' ) {
$paths[] = '/add-to-cart/*';
$paths[] = '/wishlist-toggle/*';
}
return $paths;
}, 10, 2 );
Parameters: array $paths, string $mode (prefetch or prerender).
File: WordPress Core 6.8+ | src/Frontend/SpeculationRulesEmitter.php (fallback path only).
Language Switcher
perflocale/switcher/languages
Filter the languages available in the switcher.
Parameters: array $languages - Array of language objects.
File: src/Frontend/LanguageSwitcher.php
perflocale/switcher/template
Filter the template file path used to render the switcher.
Parameters:
string $template- Template file path.array $args- Switcher arguments.
File: src/Frontend/LanguageSwitcher.php
perflocale/switcher/output
Filter the final HTML output of the language switcher.
Parameters:
string $html- Rendered HTML.array $args- Switcher arguments.
File: src/Frontend/LanguageSwitcher.php
perflocale/switcher/link_attrs
Filter the HTML attributes for each per-language link in the switcher - inline, dropdown, list, and flags renderers alike. Add analytics data-*, inject rel / title, override hreflang, or append additional CSS classes without forking the template.
// Add GA4 + Hotjar data attributes to every switcher link.
add_filter( 'perflocale/switcher/link_attrs', function ( array $attrs, object $lang, string $current_slug, array $args ): array {
$attrs['data-lang'] = $lang->slug;
$attrs['data-gtm-event'] = 'language_switch';
$attrs['data-hj-track'] = 'lang-switcher';
return $attrs;
}, 10, 4 );
// Add rel="alternate" for better SEO signal + hreflang override.
add_filter( 'perflocale/switcher/link_attrs', function ( array $attrs, object $lang ): array {
$attrs['rel'] = 'alternate';
$attrs['hreflang'] = $lang->locale ? str_replace( '_', '-', strtolower( $lang->locale ) ) : $lang->slug;
return $attrs;
}, 10, 2 );
Values are escaped via esc_attr() (or esc_url() for href). Boolean true emits an HTML boolean attribute; false / null omits the attribute entirely. Attribute names are sanitized to [A-Za-z0-9:_-].
Parameters:
array $attrs- Attribute name => value pairs.object $lang- Language object for this link (slug,name,native_name,locale,flag,text_direction).string $current_slug- Currently active language slug.array $args- Switcher arguments (template / display / layout / className …).
Args: 4
File: src/Frontend/LanguageSwitcher.php
String Translation
perflocale/string/translate
Filter a translated string before it is returned.
Parameters:
string $translated- The translated string.string $text- The original string.string $domain- Text domain.string $lang_slug- Current language slug.
File: src/String/StringTranslation.php
perflocale/strings/scanner/excluded_paths
Filter directory patterns excluded from string scanning.
Parameters: array $excluded - Array of directory patterns.
File: src/String/StringScanner.php
perflocale/string/needs_update
Fires when a registered string's source text changes and its existing translations are marked as needing review. Useful for triggering automated re-translation, sending notifications, or logging changes.
add_action( 'perflocale/string/needs_update', function ( int $string_id, string $old_text, string $new_text, string $domain, string $context ): void {
// Example: log the change or trigger auto-translation.
error_log( "String #{$string_id} changed in {$domain}/{$context}" );
}, 10, 5 );
Parameters:
int $string_id- The new string's ID.string $old_text- The previous source text that was replaced.string $new_text- The new source text.string $domain- Text domain (e.g.perflocale,woocommerce).string $context- Translation context (e.g.workflow_email_subject,email_subject_new_order).
Args: 5
File: src/Database/Repository/StringRepository.php
Machine Translation
perflocale/machine_translation/providers
Filter the registered machine translation providers.
Parameters: array $providers - Array of provider objects.
File: src/MachineTranslation/TranslationService.php
perflocale/mt/pre_translate
Filter texts before they are sent to a machine translation provider.
Parameters:
array $texts- Array of text strings.string $source_lang- Source language code.string $target_lang- Target language code.string $provider_id- Provider identifier.
File: src/MachineTranslation/TranslationService.php
perflocale/mt/post_translate
Filter translated texts after they are returned from a machine translation provider.
Parameters:
array $translated- Translated text strings.array $originals- Original text strings.string $target_lang- Target language code.string $provider_id- Provider identifier.
File: src/MachineTranslation/TranslationService.php
perflocale/machine_translation/text_before_send
Filter individual text before sending to a provider.
Parameters:
string $text- Text to translate.string $provider_id- Provider identifier (e.g.,'deepl','google').string $target_lang- Target language code.
File: src/MachineTranslation/Provider/*.php
perflocale/machine_translation/result
Filter individual translation result from a provider.
Parameters:
string $translated- Translated text.string $original- Original text.string $provider_id- Provider identifier.
File: src/MachineTranslation/Provider/*.php
perflocale/mt/request_args
Filter the wp_remote_request() arguments right before an outbound call to a machine-translation provider. Use it to raise the timeout for bulk jobs, inject proxy/tracing headers, set a custom user-agent, or route calls through a corporate gateway.
// 90-second timeout + corporate proxy + custom UA for the DeepL endpoint.
add_filter( 'perflocale/mt/request_args', function ( array $args, string $url, string $provider_id ): array {
if ( $provider_id !== 'deepl' ) {
return $args;
}
$args['timeout'] = 90;
$args['user-agent'] = 'AcmeCorp-Translation/1.0';
$args['headers']['X-Forwarded-Via'] = 'corp-gw';
return $args;
}, 10, 3 );
The default SSRF validation runs before this filter and cannot be bypassed: the URL is verified against the internal/private-IP blocklist first, then the filter gets to tweak args.
Parameters:
array $args-wp_remote_request()argument array (timeout, headers, body, method, sslverify …).string $url- Destination URL (already SSRF-validated).string $provider_id- Provider slug (deepl,google,microsoft,libretranslate,external-agency).
Args: 3
File: src/MachineTranslation/AbstractProvider.php
Query Filtering
perflocale/query/include_all_languages
Override language filtering to include all languages in a query.
add_filter( 'perflocale/query/include_all_languages', function ( bool $include, WP_Query $query ): bool {
if ( $query->get( 'post_type' ) === 'faq' ) {
return true; // Show FAQs in all languages.
}
return $include;
}, 10, 2 );
Parameters:
bool $include- Whether to include all languages (defaultfalse).WP_Query $query- The query object.
File: src/Translation/PostQueryFilter.php
Addons
perflocale/addons/registered
Filter the list of registered addons.
Parameters: array $addons - Array of addon definitions.
File: src/Addon/AddonRegistry.php
perflocale/addon/is_compatible
Filter whether an addon is compatible with the current environment.
Parameters:
bool $compatible- Compatibility result.string $addon_id- Addon identifier.
File: src/Addon/AddonRegistry.php
WooCommerce
perflocale/woocommerce/exchange_rate_providers
Register or modify exchange rate API providers.
Parameters: array $providers - Provider definitions (see Exchange Rates docs).
File: src/WooCommerce/ExchangeRateSync.php
perflocale/woocommerce/exchange_rates_fetched
Filter rates after they are fetched from the API but before saving. Use to add a markup or override specific rates.
Parameters:
array $rates- Currency code => exchange rate.string $base_currency- WooCommerce base currency code.string $provider_id- Selected provider ID.
File: src/WooCommerce/ExchangeRateSync.php
perflocale/woocommerce/synced_product_fields
Control which product meta keys are synced across language variants.
Parameters:
array $fields- Meta key list.int $product_id- Product being synced.
File: src/WooCommerce/InventorySync.php
perflocale/woocommerce/skip_inventory_sync
Skip inventory sync for a specific product.
Parameters:
bool $skip- Default false.int $product_id- Product being synced.
File: src/WooCommerce/InventorySync.php
perflocale/woocommerce/translate_string
Override string translation for WooCommerce gateway/shipping titles.
Parameters:
string|null $translated- Translated string or null (no override).string $original- Original string.string $slug- Current language slug.string $context- Translation context (gateway ID or empty).
Args: 4
File: src/WooCommerce/WcStringTranslation.php
perflocale/woocommerce/translatable_email_ids
Filter the WooCommerce email IDs whose subjects, headings, and additional content are translatable via the String Translation system.
add_filter( 'perflocale/woocommerce/translatable_email_ids', function ( array $ids ): array {
$ids[] = 'customer_stock_notification'; // Add a custom WC email type.
return $ids;
} );
Parameters: array $ids - Array of WooCommerce email ID strings.
Default: new_order, cancelled_order, failed_order, customer_on_hold_order, customer_processing_order, customer_completed_order, customer_refunded_order, customer_invoice, customer_note
Args: 1
File: src/WooCommerce/EmailTranslation.php
GeoIP Redirect
perflocale/geo/providers
Filter available GeoIP providers. Add custom providers or modify built-in ones.
add_filter( 'perflocale/geo/providers', function ( array $providers ): array {
$providers['my_provider'] = [
'name' => 'My GeoIP Provider',
'needs_key' => true,
'key_setting' => 'geo_my_provider_key',
'fetch_callback' => function ( string $ip, \PerfLocale\Settings $settings ): string {
// Return two-letter country code or ''.
return 'US';
},
];
return $providers;
} );
Parameters: array $providers - Associative array of provider definitions.
File: src/Router/GeoRedirect.php
perflocale/geo/country_code
Filter the detected country code after a GeoIP lookup.
Parameters: string $country_code, string $ip
File: src/Router/GeoRedirect.php
perflocale/geo/redirect_language
Override the language slug selected for redirect.
Parameters: string $language_slug, string $country_code, string $ip
File: src/Router/GeoRedirect.php
perflocale/geo/country_map
Filter the country-to-language mapping array.
Parameters: array $map - Associative array of language slug => comma-separated country codes.
File: src/Router/GeoRedirect.php
perflocale/geo/should_redirect
Final short-circuit before the outbound IP-lookup HTTP request is made. Return false to skip GeoIP redirection entirely for the current request - no provider call is issued. Complements the existing perflocale/privacy/consent_given gate.
// Skip GeoIP redirects for logged-in users, admin URLs, and affiliate campaigns.
add_filter( 'perflocale/geo/should_redirect', function ( bool $should ): bool {
if ( is_user_logged_in() ) {
return false; // Respect the visitor’s saved preference.
}
if ( ! empty( $_GET['utm_campaign'] ) ) {
return false; // Don’t second-guess campaign traffic.
}
$path = wp_parse_url( $_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH ) ?: '/';
if ( str_starts_with( $path, '/api/' ) || str_starts_with( $path, '/webhook/' ) ) {
return false;
}
return $should;
} );
Parameters: bool $should_redirect - default true.
File: src/Router/GeoRedirect.php
perflocale/fallback/redirect_status
HTTP status code used when a language fallback redirect fires. Default 302 (temporary). Return 301 for permanent SEO consolidation. Allowed values: {301, 302, 307, 308}; anything else silently reverts to 302.
// Permanent redirects for en_US → en_GB specifically.
add_filter( 'perflocale/fallback/redirect_status', function ( int $status, string $from, string $to ): int {
return ( $from === 'en-us' && $to === 'en-gb' ) ? 301 : $status;
}, 10, 3 );
Parameters:
int $status- default 302.string $from_slug- current (missing) language slug.string $to_slug- resolved fallback language slug.int $from_post_id- source post being rendered.int $to_post_id- target post ID (0when redirecting to the language homepage).
File: src/Translation/PostQueryFilter.php
Privacy
perflocale/privacy/consent_given
Gate for the automatic GeoIP + browser-language redirects. Return false to suppress both until the visitor has granted consent. Default true - redirects fire immediately, matching the pre-consent-framework behaviour.
// Hook any consent-management plugin (Cookiebot, Complianz, Iubenda,
// OneTrust...) that exposes a "consent given for marketing/functional
// cookies" check.
add_filter( 'perflocale/privacy/consent_given', function (): bool {
if ( ! function_exists( 'complianz_has_consent' ) ) {
return true; // Consent plugin not active — behave as before.
}
return (bool) complianz_has_consent( 'functional' );
} );
Parameters: bool $granted - default true.
File: src/Router/LanguageRouter.php, src/Router/GeoRedirect.php
REST API
perflocale/api/config
Filter the JSON payload returned by GET /perflocale/v1/config before it is cached and served to edge runtimes (Cloudflare Worker, Vercel Edge, Netlify Edge, …). Use it to carry feature flags, per-language A/B variants, fallback chains, or any routing metadata the edge needs without an extra origin round-trip.
// Expose feature flags + fallback chain to the edge.
add_filter( 'perflocale/api/config', function ( array $payload ): array {
$payload['feature_flags'] = [
'new_checkout' => get_option( 'acme_new_checkout_enabled', false ),
'promo_banner' => get_option( 'acme_promo_banner_active', false ),
];
$payload['fallback_chain'] = [
'en-gb' => [ 'en-us', 'en' ],
'de-at' => [ 'de', 'en' ],
];
return $payload;
} );
The result is stored in the 3-layer cache and contributes to the ETag. Keep additions deterministic (no timestamps, no request-specific data). If the custom fields change at runtime, call \PerfLocale\Api\ConfigController::invalidate() or update any option/setting that already triggers perflocale/settings/updated.
Parameters: array $payload - The full config payload (version, url_mode, default_slug, hide_default_prefix, excluded_paths, detection_order, edge_hint_header, edge_hint_cookie, languages).
File: src/Api/ConfigController.php
perflocale/api/languages_public
Whether the public read endpoints GET /perflocale/v1/languages and GET /perflocale/v1/languages/{slug} may be accessed anonymously. Default true - the payload is a strict subset of what is already rendered to visitors (switcher, hreflang tags, URL structure), so there is nothing private to protect. Return false to require the read capability (any logged-in user) instead. Write endpoints are unaffected - they always require perflocale_manage_languages.
// Block anonymous reads of /languages.
add_filter( 'perflocale/api/languages_public', '__return_false' );
Parameters:
bool $public- defaulttrue.WP_REST_Request $request- current request.
Args: 2
File: src/Api/LanguagesController.php
Admin
perflocale/admin/post_list_columns
Filter the columns added to the post list table.
Parameters:
array $columns- Column definitions.string $post_type- Current post type.
File: src/Admin/PostListColumns.php
perflocale/fse/translatable_blocks
Filter which block types are translatable in the Full Site Editor.
Parameters: array $blocks - Array of block type names.
File: src/Admin/SiteEditorIntegration.php
Roles & Permissions
perflocale/roles/editor_caps
Filter the capabilities granted to the Editor role on plugin activation. Return an empty array to prevent Editors from receiving any PerfLocale capabilities. Return a subset to restrict them to specific ones. The Administrator role is not affected.
Parameters: array<string, bool> $caps - Map of capability => grant. Default: translate, manage_translations, manage_glossary, use_mt, bulk_translate, view_analytics.
File: src/Admin/TranslatorRole.php
// Remove ALL PerfLocale capabilities from the Editor role.
add_filter( 'perflocale/roles/editor_caps', '__return_empty_array' );
// Restrict Editors to translation-only access (no management capabilities).
add_filter( 'perflocale/roles/editor_caps', function ( array $caps ): array {
return [
'perflocale_translate' => true,
'perflocale_use_mt' => true,
'perflocale_view_analytics' => true,
];
} );
Note: Capability grants are stored in the database and only written once per version. To re-apply the filter to an existing install, reset the version flag: delete_option( 'perflocale_caps_version' ); then reload any admin page.
perflocale/roles/cap_roles
Filter which WordPress roles have PerfLocale capabilities removed on plugin deactivation or uninstall. Fires in three places: TranslatorRole::remove_roles(), and both the full-wipe and preserve-data branches of uninstall.php.
Parameters: string[] $roles - Array of role slugs. Default: ['administrator', 'editor'].
File: src/Admin/TranslatorRole.php, uninstall.php
// Do not strip caps from the Editor role on deactivation/uninstall.
add_filter( 'perflocale/roles/cap_roles', function ( array $roles ): array {
return array_diff( $roles, [ 'editor' ] );
} );
// Also clean up a custom role that was granted caps programmatically.
add_filter( 'perflocale/roles/cap_roles', function ( array $roles ): array {
\$roles[] = 'shop_manager';
return \$roles;
} );
Actions
Plugin Lifecycle
perflocale/activated
Fires after the plugin is activated.
Parameters: string $version - Plugin version.
File: src/Activator.php
perflocale/deactivated
Fires after the plugin is deactivated.
File: src/Deactivator.php
perflocale/loaded
Fires after PerfLocale is fully bootstrapped and all services are registered.
add_action( 'perflocale/loaded', function ( \PerfLocale\Plugin $plugin ): void {
// Safe to use all PerfLocale services here.
$router = $plugin->get( 'router' );
} );
Parameters: Plugin $plugin - The plugin container instance.
File: src/Bootstrap.php
perflocale/upgraded
Fires after a database schema migration completes.
Parameters:
string $old_version- Previous schema version.string $new_version- New schema version.
Args: 2
File: src/Database/Migrator.php
perflocale/updated
Fires after the plugin code version changes (e.g., updated from 1.0.0 to 1.1.0). Unlike perflocale/upgraded which fires on DB schema changes, this fires on any code version bump. Useful for version-specific non-DB tasks like migrating settings or regenerating files.
add_action( 'perflocale/updated', function ( string $old_version, string $new_version ): void {
if ( version_compare( $old_version, '1.1.0', '<' ) ) {
// One-time task for 1.1.0 update.
}
}, 10, 2 );
Parameters:
string $old_version- Previous plugin version.string $new_version- Current plugin version.
Args: 2
File: src/Database/Migrator.php
perflocale/strings/after_scan
Fires after the "Scan for Strings" action completes. Addons hook here to register non-gettext translatable strings (e.g., WooCommerce email subjects, attribute labels).
add_action( 'perflocale/strings/after_scan', function (): void {
// Register custom non-gettext strings here.
} );
Args: 0
File: src/Admin/AdminController.php
Language Events
perflocale/language/detected
Fires after the current language is detected from the request.
add_action( 'perflocale/language/detected', function ( string $slug, string $method ): void {
// $method is 'url', 'cookie', 'browser', or 'default'.
if ( $method === 'browser' ) {
// First-time visitor, language detected from browser.
}
}, 10, 2 );
Parameters:
string $slug- Detected language slug.string $method- Detection method used.
File: src/Router/LanguageRouter.php
perflocale/language/switched
Fires when the language is switched programmatically via switch_language().
Parameters:
string $slug- New language slug.string $previous- Previous language slug.
File: src/Router/LanguageRouter.php
perflocale/language/added
Fires after a new language is added.
Parameters: object $language - The language object.
File: src/Database/Repository/LanguageRepository.php
perflocale/language/updated
Fires after a language is updated.
Parameters:
object $language- Updated language object.array $old- Previous language data.
File: src/Database/Repository/LanguageRepository.php
perflocale/language/deleted
Fires after a language is deleted.
Parameters:
int $id- Language ID.string $slug- Language slug.
File: src/Database/Repository/LanguageRepository.php
perflocale/default_language/changed
Fires after the default language is changed.
Parameters:
object $new_default- New default language.object $old_default- Previous default language.
File: src/Database/Repository/LanguageRepository.php
Translation Events
perflocale/translation/created
Fires after a translation post or term is created.
add_action( 'perflocale/translation/created', function ( int $new_id, string $type, string $target_slug, int $source_id ): void {
if ( $type === 'post' ) {
// Correlate source → translation, e.g. send to CRM / analytics.
my_crm_record_translation_pair( $source_id, $new_id, $target_slug );
}
}, 10, 4 );
Parameters:
int $new_id- New post/term ID.string $type- Object type ('post'or'term').string $target_slug- Target language slug.int $source_id- Source post/term ID the translation was created from. Useful for correlating analytics, CRM entries, or webhook payloads without a secondary translation-group lookup.
Args: 4
File: src/Translation/PostTranslationManager.php, src/Translation/TermTranslationManager.php
perflocale/translation/linked
Fires when an object is linked to a translation group.
Parameters:
int $group_id- Translation group ID.int $object_id- Object ID.int $language_id- Language ID.
File: src/Database/Repository/TranslationGroupRepository.php
perflocale/translation/status_changed
Fires when a translation's status changes (e.g., draft to published).
Parameters:
int $object_id- Object ID.string $status- New status.int $language_id- Language ID.
File: src/Database/Repository/TranslationGroupRepository.php
perflocale/content/changed
Fires when source content changes, marking translations as potentially outdated.
Parameters:
int $object_id- Object ID.string $type- Object type value.int $group_id- Translation group ID.
File: src/Translation/ContentChangeDetector.php
Settings
perflocale/settings/updated
Fires after plugin settings are saved.
Parameters:
array $merged- Merged (new) settings array.array $current- Previous settings array.
File: src/Settings.php
Workflow
perflocale/workflow/assigned
Fires when a translation is assigned to a user.
Parameters:
int $object_id- Post/term ID.string $object_type- Object type.int $language_id- Language ID.int $user_id- Assigned user ID.
File: src/Admin/TranslatorRole.php
perflocale/workflow/status_changed
Fires when a workflow status changes.
add_action( 'perflocale/workflow/status_changed', function ( int $object_id, string $type, int $lang_id, string $status ): void {
if ( $status === 'approved' ) {
// Send Slack notification.
}
}, 10, 4 );
Parameters:
int $object_id- Post/term ID.string $object_type- Object type.int $language_id- Language ID.string $status- New workflow status.
File: src/Admin/TranslatorRole.php
IndexNow (added in 1.2.0)
perflocale/indexnow/push_result
Fires after every IndexNow push attempt (per-host). Because wp_remote_post runs with blocking => false, $response reflects the dispatch result (usually a truncated WP_Error or an array with no body), not the final HTTP status. Use this to log pushes, track deliveries, or count daily volume against search-engine rate limits.
add_action( 'perflocale/indexnow/push_result', function ( $response, string $endpoint, string $host, array $urls, array $context ): void {
error_log( sprintf(
'[IndexNow] %s host=%s urls=%d trigger=%s',
is_wp_error( $response ) ? 'FAIL: ' . $response->get_error_message() : 'OK',
$host,
count( $urls ),
(string) ( $context['trigger_post_id'] ?? '' )
) );
}, 10, 5 );
Parameters: WP_Error|array $response, string $endpoint, string $host, array $urls, array $context.
File: src/Seo/IndexNowPusher.php
Machine Translation
perflocale/machine_translation/before
Fires before machine translation starts for a post.
Parameters:
int $post_id- Post ID.string $provider_id- Provider identifier.
File: src/MachineTranslation/TranslationService.php
perflocale/machine_translation/after
Fires after machine translation completes successfully.
Parameters:
int $post_id- Post ID.string $provider_id- Provider identifier.mixed $result- Translation result.
File: src/MachineTranslation/TranslationService.php
perflocale/machine_translation/failed
Fires when machine translation fails with an exception.
Parameters:
int $post_id- Post ID.string $provider_id- Provider identifier.Exception $exception- The exception.
File: src/MachineTranslation/TranslationService.php
Cache
perflocale/cache/flush_all
Fires after the entire plugin cache is flushed.
File: src/Cache/CacheManager.php
perflocale/cache/flush_object
Fires after cache is flushed for a specific object.
Parameters:
int $object_id- Object ID.string $object_type- Object type.
File: src/Cache/CacheManager.php
Addons
perflocale/addon/activated
Fires when an addon is activated (booted).
Parameters: string $addon_id - Addon identifier.
File: src/Addon/AddonRegistry.php
perflocale/addons/loaded
Fires after all addons have been loaded.
File: src/Addon/AddonRegistry.php
WooCommerce
perflocale/woocommerce/exchange_rates_synced
Fires after exchange rates are successfully fetched and saved.
Parameters:
array $rates- Currency code => exchange rate.string $base_currency- WooCommerce base currency code.string $provider_id- Provider that was used.
File: src/WooCommerce/ExchangeRateSync.php
perflocale/woocommerce/inventory_synced
Fires after inventory fields are synced across language variants.
Parameters:
int $product_id- Source product ID.array $synced_ids- IDs of sibling translations that were updated.array $fields- Meta keys that were synced.
File: src/WooCommerce/InventorySync.php
GeoIP Redirect
perflocale/geo/redirected
Fires after a GeoIP redirect has been performed.
Parameters:
string $language_slug- The language the visitor was redirected to.string $country_code- Detected country code.string $ip- Visitor IP address.
File: src/Router/GeoRedirect.php
Edge Integration (filters)
perflocale/edge/enabled
Programmatic override for whether the edge-integration feature is active. When returned true, PerfLocale publishes /wp-json/perflocale/v1/config and honours the edge_hint detection method, regardless of the edge_integration_enabled setting value.
add_filter( 'perflocale/edge/enabled', '__return_true' );
Parameters: bool $enabled - Current effective state.
File: src/Settings.php
perflocale/edge/hint_header
Rename the HTTP header used to carry the edge-selected language.
add_filter( 'perflocale/edge/hint_header', fn() => 'X-Vercel-Lang' );
Parameters: string $header_name - Default X-PerfLocale-Lang.
File: src/Router/LanguageRouter.php
perflocale/edge/hint_cookie
Rename the cookie used as fallback for edge-selected language.
add_filter( 'perflocale/edge/hint_cookie', fn() => 'my_lang_cookie' );
Parameters: string $cookie_name - Default perflocale_edge_lang.
File: src/Router/LanguageRouter.php
perflocale/edge/accept_hint
Veto a specific edge-hint per request. Return false to reject an otherwise valid hint (e.g. behind a reverse proxy that mis-forwards headers).
add_filter( 'perflocale/edge/accept_hint', function ( bool $accept, string $slug ): bool {
if ( ! empty( $_SERVER['HTTP_X_INTERNAL_PROBE'] ) ) {
return false;
}
return $accept;
}, 10, 2 );
Parameters:
bool $accept- Defaulttrue.string $slug- Candidate language slug from header/cookie.
File: src/Router/LanguageRouter.php
CDN Cache-Tag Headers (filters + action)
perflocale/cache_tags/enabled
Programmatic override for Cache-Tag header emission. Returning false kills the feature without touching the setting.
Parameters: bool $enabled - Current effective state.
File: src/Settings.php
perflocale/cache_tags/header_name
Change the response-header name. Useful for Fastly (Surrogate-Key) or custom CDNs.
add_filter( 'perflocale/cache_tags/header_name', fn() => 'Surrogate-Key' );
Parameters: string $name - Default Cache-Tag.
File: src/Frontend/CacheTagEmitter.php
perflocale/cache_tags/tags
Modify the list of tags emitted for the current request. Tags are ASCII-only ([A-Za-z0-9\-_:.]), truncated per-entry to 128 chars and capped at 32 per response.
add_filter( 'perflocale/cache_tags/tags', function ( array $tags ): array {
$tags[] = 'theme:' . get_template();
return $tags;
} );
Parameters: array $tags - Sanitised tag list.
File: src/Frontend/CacheTagEmitter.php
perflocale/cache_tags/max_header_length
Response-header byte budget (default 8000). Tags overflowing the budget are dropped silently.
Parameters: int $max - Default 8000.
File: src/Frontend/CacheTagEmitter.php
perflocale/cache_tags/purge (action)
Fires on relevant content-change hooks with the tags that a host-specific purge plugin should flush. PerfLocale itself does not issue remote purges - that’s left to integrators (Cloudflare, Bunny, Fastly).
add_action( 'perflocale/cache_tags/purge', function ( array $tags ): void {
my_cdn_purge_tags( $tags );
} );
Parameters: array $tags - Tags to purge.
File: src/Frontend/CacheTagEmitter.php
SEO Schema Enrichment
perflocale/seo/schema_enrichment_enabled
Programmatic override for JSON-LD schema enrichment across the six built-in SEO addons (Yoast, AIOSEO, Rank Math, SEOPress, Slim SEO, The SEO Framework). Default is true; return false to suppress inLanguage + workTranslation additions.
Parameters: bool $enabled - Current effective state.
File: src/Settings.php
Workflow Email
perflocale/workflow/email
Filter the outgoing workflow email before wp_mail() dispatches it. Useful for routing admin mail through a transactional provider, rewriting recipients, or appending localized footers.
add_filter( 'perflocale/workflow/email', function ( array $mail, WP_User $user ): array {
$mail['headers'][] = 'X-Transactional: workflow';
$mail['body'] .= "\n\n" . __( 'This is an automated notification.', 'my-theme' );
return $mail;
}, 10, 2 );
Parameters:
array $mail-{ to, subject, body, vars }.WP_User $user- Recipient.
File: src/Admin/WorkflowNotifier.php
Rate Limits & Scan Caps
perflocale/mt/rate_limit
Per-user hourly ceiling on POST /perflocale/v1/machine-translate calls. Return 0 to disable rate limiting entirely.
add_filter( 'perflocale/mt/rate_limit', fn() => 1000 );
Parameters: int $limit - Default 500 requests per hour.
File: src/Api/MachineTranslateController.php
perflocale/glossary/scan_limit
Maximum posts scanned per invocation of the auto-glossary candidate scanner. Clamped to [10, 5000].
add_filter( 'perflocale/glossary/scan_limit', fn() => 2000 );
Parameters: int $limit - Default 500.
File: src/Translation/GlossaryScanner.php
Additional extension points
The following hooks cover more specialised extension points. They are stable and safe to use in production.
Addons & integrations
perflocale/addons/register
Fires after built-in addons are registered so third parties can register their own addon instances before boot.
add_action( 'perflocale/addons/register', function ( \PerfLocale\Addon\AddonRegistry $registry ): void {
$registry->register( new \My\Plugin\MyAddon() );
} );
Parameters: \PerfLocale\Addon\AddonRegistry $registry.
File: src/Addon/AddonRegistry.php
perflocale/addons/registry
Filter the array of known addons as rendered on the Addons admin page.
add_filter( 'perflocale/addons/registry', function ( array $addons ): array {
unset( $addons['legacy-addon-id'] );
return $addons;
} );
Parameters: array $addons - Map keyed by addon ID.
File: src/Admin/Pages/AddonsPage.php
perflocale/addons/suppress_hreflang
Let an SEO-plugin addon suppress PerfLocale's own hreflang output when it already emits the tags. Returns a boolean.
add_filter( 'perflocale/addons/suppress_hreflang', function ( bool $suppress ): bool {
return true; // Let the host SEO plugin own hreflang entirely.
} );
Parameters: bool $suppress - Default false.
File: src/Addon/HreflangCoordinator.php
perflocale/addon/before_migrate
Fires once per migration step, before the addon's migrate_to() runs.
add_action( 'perflocale/addon/before_migrate', function ( $addon, int $stored, int $target ): void {
error_log( sprintf( 'addon=%s stored=%d -> step=%d', $addon->get_id(), $stored, $target ) );
}, 10, 3 );
Parameters: AddonInterface&HasSchema $addon, int $stored, int $target.
Args: 3
File: src/Addon/AddonSchemaManager.php
perflocale/addon/migrated
Fires after each successful addon migration step.
add_action( 'perflocale/addon/migrated', function ( $addon, int $version ): void {
// Warm caches, record telemetry, etc.
}, 10, 2 );
Parameters: AddonInterface&HasSchema $addon, int $version.
Args: 2
File: src/Addon/AddonSchemaManager.php
perflocale/addon/migration_failed
Fires when an addon migration step throws. The addon is not quarantined by this filter - observe or re-raise as needed.
add_action( 'perflocale/addon/migration_failed', function ( $addon, int $version, \Throwable $e ): void {
error_log( sprintf( '[perflocale] %s v%d failed: %s', $addon->get_id(), $version, $e->getMessage() ) );
}, 10, 3 );
Parameters: AddonInterface&HasSchema $addon, int $version, \Throwable $e.
Args: 3
File: src/Addon/AddonSchemaManager.php
perflocale/addon/manifest_written
Fires after an addon manifest is re-persisted to disk - used by the uninstall pipeline to know what to purge even when the addon is gone.
add_action( 'perflocale/addon/manifest_written', function ( string $addon_id, array $manifest ): void {
// Audit-log manifest changes.
}, 10, 2 );
Parameters: string $addon_id, array $manifest.
Args: 2
File: src/Addon/AddonManifestWriter.php
perflocale/addon/before_uninstall
Fires before an addon's stored data is purged. Receives the computed PurgePlan snapshot.
add_action( 'perflocale/addon/before_uninstall', function ( string $addon_id, \PerfLocale\Addon\PurgePlan $plan ): void {
// Back up rows referenced by $plan before PerfLocale drops them.
}, 10, 2 );
Parameters: string $addon_id, \PerfLocale\Addon\PurgePlan $plan.
Args: 2
File: src/Addon/AddonUninstaller.php
perflocale/addon/meta_purge_batch
Fires once per batched DELETE during addon uninstall meta cleanup.
add_action( 'perflocale/addon/meta_purge_batch', function ( string $type, int $deleted, int $total ): void {
// $type is 'postmeta' | 'termmeta' | 'usermeta' etc.
}, 10, 3 );
Parameters: string $type, int $deleted, int $total.
Args: 3
File: src/Addon/AddonUninstaller.php
perflocale/addon/uninstalled
Fires after an addon's uninstall pipeline completes.
add_action( 'perflocale/addon/uninstalled', function ( string $addon_id, \PerfLocale\Addon\PurgeResult $result ): void {
// $result reports counts per table / meta type.
}, 10, 2 );
Parameters: string $addon_id, \PerfLocale\Addon\PurgeResult $result.
Args: 2
File: src/Addon/AddonUninstaller.php
Addon settings UI
perflocale/settings/addon_subtabs
Register a Settings subtab belonging to your addon. Return a map of slug => label.
add_filter( 'perflocale/settings/addon_subtabs', function ( array $subtabs ): array {
$subtabs['my-addon'] = __( 'My Addon', 'my-plugin' );
return $subtabs;
} );
Parameters: array $subtabs.
File: src/Admin/AdminController.php
perflocale/settings/render_addon_subtab
Render the form fields for a registered addon subtab.
add_action( 'perflocale/settings/render_addon_subtab', function ( string $subtab, \PerfLocale\Settings $settings ): void {
if ( $subtab !== 'my-addon' ) {
return;
}
// echo <tr> form-table rows here.
}, 10, 2 );
Parameters: string $subtab, \PerfLocale\Settings $settings.
Args: 2
File: src/Admin/Pages/SettingsPage.php
perflocale/settings/addon_subtab_after
Fires after the addon subtab form-table, right before the submit button - use for secondary actions.
add_action( 'perflocale/settings/addon_subtab_after', function ( string $subtab, \PerfLocale\Settings $settings ): void {
if ( $subtab === 'my-addon' ) {
echo '<p class="description">' . esc_html__( 'Need help? See our docs.', 'my-plugin' ) . '</p>';
}
}, 10, 2 );
Parameters: string $subtab, \PerfLocale\Settings $settings.
Args: 2
File: src/Admin/Pages/SettingsPage.php
perflocale/settings/extract_addon_values
Filter the sanitized settings being written for an addon subtab before they are merged into perflocale_settings.
add_filter( 'perflocale/settings/extract_addon_values', function ( array $values, string $tab ): array {
if ( $tab !== 'my-addon' ) {
return $values;
}
$values['my_key'] = sanitize_text_field( wp_unslash( $_POST['my_key'] ?? '' ) );
return $values;
}, 10, 2 );
Parameters: array $values, string $tab.
Args: 2
File: src/Admin/Pages/SettingsPage.php
GeoIP
perflocale/geo/visitor_ip
Override the visitor IP used for GeoIP lookups. Useful for testing on localhost or for honouring a specific proxy header.
add_filter( 'perflocale/geo/visitor_ip', function ( string $ip ): string {
return $_SERVER['HTTP_X_REAL_IP'] ?? $ip;
} );
Parameters: string $ip.
File: src/Router/GeoRedirect.php
perflocale/geo/lookup_country
Short-circuit the GeoIP lookup with your own data source (e.g. a Cloudflare CF-IPCountry header).
add_filter( 'perflocale/geo/lookup_country', function ( string $country, string $ip ): string {
if ( ! empty( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) {
return strtoupper( (string) $_SERVER['HTTP_CF_IPCOUNTRY'] );
}
return $country;
}, 10, 2 );
Parameters: string $country (ISO-3166-1 alpha-2, or empty), string $ip.
Args: 2
File: src/Router/GeoRedirect.php
Routing & detection
perflocale/accept_language_limit
Maximum number of ranked entries parsed out of the Accept-Language HTTP header when matching the visitor's preferred language.
add_filter( 'perflocale/accept_language_limit', fn() => 5 );
Parameters: int $limit - Default 20.
File: src/Router/LanguageRouter.php
perflocale/query/child_post_types
Post types treated as children of their parent (e.g. WooCommerce variations). Query filtering follows the parent's language to avoid split listings.
add_filter( 'perflocale/query/child_post_types', function ( array $types ): array {
$types[] = 'my_variant_cpt';
return $types;
} );
Parameters: array $types - Default [ 'product_variation' ].
File: src/Translation/PostQueryFilter.php
Language switcher (menu placement)
perflocale/switcher/add_to_menu
Return true from within wp_nav_menu_items filtering to have the language switcher appended to a nav menu without an explicit menu location.
add_filter( 'perflocale/switcher/add_to_menu', function ( bool $add, \stdClass $args ): bool {
return $args->theme_location === 'primary';
}, 10, 2 );
Parameters: bool $add, \stdClass $args.
Args: 2
File: src/Frontend/LanguageSwitcher.php
perflocale/switcher/menu_locations
Whitelist specific theme menu locations that should receive the language switcher.
add_filter( 'perflocale/switcher/menu_locations', function ( array $locations, \stdClass $args ): array {
return [ 'primary', 'header-utility' ];
}, 10, 2 );
Parameters: array $locations, \stdClass $args.
Args: 2
File: src/Frontend/LanguageSwitcher.php
Machine translation
perflocale/mt/allowed_html
Filter the wp_kses-style allowed-tags map applied to translated HTML returned by MT providers. Tighten or loosen to taste.
add_filter( 'perflocale/mt/allowed_html', function ( array $allowed ): array {
$allowed['mark'] = [];
return $allowed;
} );
Parameters: array $allowed.
File: src/MachineTranslation/TranslationService.php
Strings & content
perflocale/strings/regenerate_files
Fires after the cache invalidator decides a .mo regeneration is warranted. Hook in to trigger downstream compilation or to piggyback on the same cache-clear window.
add_action( 'perflocale/strings/regenerate_files', function ( \PerfLocale\Cache\CacheManager $cache ): void {
// Custom follow-up work.
} );
Parameters: \PerfLocale\Cache\CacheManager $cache.
File: src/Cache/CacheInvalidator.php
perflocale/translation/dangerous_meta_patterns
Regex list for meta keys that must never be translated or synced (serialised PHP objects, private flags, etc.). Patterns are matched against the meta key.
add_filter( 'perflocale/translation/dangerous_meta_patterns', function ( array $patterns ): array {
$patterns[] = '/^_my_encrypted_/';
return $patterns;
} );
Parameters: array $patterns.
File: src/Translation/PostTranslationManager.php
Cache & SEO
perflocale/cache/flush_archive_hreflang
Fires when the archive-page hreflang cache is invalidated for a post. Use it to purge an external CDN cache for the same URL group.
add_action( 'perflocale/cache/flush_archive_hreflang', function ( int $post_id ): void {
my_cdn_purge_archive_urls_for( $post_id );
} );
Parameters: int $post_id.
File: src/Cache/CacheInvalidator.php
Admin & permissions
perflocale/abilities/enabled
Enable WordPress 6.7+ "abilities" integration for PerfLocale capabilities. Off by default while the core Abilities API is still stabilising.
add_filter( 'perflocale/abilities/enabled', '__return_true' );
Parameters: bool $enabled - Default false.
File: src/Bootstrap.php
perflocale/block_toolbar/enabled
Toggle PerfLocale's block-editor toolbar button for flagging a block as non-translatable.
add_filter( 'perflocale/block_toolbar/enabled', '__return_false' );
Parameters: bool $enabled - Default true.
File: src/Bootstrap.php
perflocale/menu/badge_post_limit
Upper bound on the number of posts scanned when computing the "pending translations" badge on the admin menu. Keeps badge counts cheap on very large sites.
add_filter( 'perflocale/menu/badge_post_limit', fn() => 10000 );
Parameters: int $limit - Default 5000.
File: src/Translation/MenuManager.php
perflocale/migration/time_limit
Override the set_time_limit() value used during admin-triggered migration imports.
add_filter( 'perflocale/migration/time_limit', fn() => 900 );
Parameters: int $seconds - Default 300.
File: src/Admin/AdminController.php
Webhooks
perflocale/webhooks/url_safe
Final gate before a webhook URL is dispatched - return false to reject. The built-in checks already block private/loopback/metadata IPs; use this filter to layer org-specific policy on top.
add_filter( 'perflocale/webhooks/url_safe', function ( bool $safe, string $url ): bool {
$host = wp_parse_url( $url, PHP_URL_HOST );
return $safe && in_array( $host, [ 'hooks.my-org.example' ], true );
}, 10, 2 );
Parameters: bool $safe, string $url.
Args: 2
File: src/Api/WebhookController.php