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

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 like featured_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_DEde-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

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 (default false).
  • 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 (0 when redirecting to the language homepage).

File: src/Translation/PostQueryFilter.php

Privacy

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 - default true.
  • 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

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 - Default true.
  • 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