key => [ domain, context, text ]. */ private array $found = array(); /** Re-entrance guard: our own code may call gettext. */ private bool $busy = false; public function __construct( private readonly CurrentLanguage $current, private readonly StringCatalog $catalog, private readonly StringRules $rules ) {} public function register(): void { Hooks::filter( 'gettext', array( $this, 'gettext' ), 99, 3 ); Hooks::filter( 'gettext_with_context', array( $this, 'gettext_with_context' ), 99, 4 ); Hooks::action( 'init', array( $this, 'decide_capture' ), 1 ); Hooks::action( 'shutdown', array( $this, 'flush' ) ); } public function gettext( mixed $translation, mixed $text, mixed $domain ): mixed { return $this->filter( $translation, $text, '', (string) $domain ); } public function gettext_with_context( mixed $translation, mixed $text, mixed $context, mixed $domain ): mixed { return $this->filter( $translation, $text, (string) $context, (string) $domain ); } /** Language of this front-end request when it is not the default one, else ''. */ public function active_lang(): string { if ( null !== $this->lang ) { return $this->lang; } if ( is_admin() || ! did_action( 'plugins_loaded' ) ) { return ''; // Too early: the URL is not known to be ours yet. Not cached. } $this->lang = $this->current->is_other_than_default() ? $this->current->get() : ''; return $this->lang; } /** Only administrators' front-end requests record strings; filter blocklingo_strings_capture. */ public function decide_capture(): void { $on = '' !== $this->active_lang() && current_user_can( 'manage_options' ); $this->capture = (bool) apply_filters( 'blocklingo_strings_capture', $on ); if ( ! $this->capture ) { $this->found = array(); } } /** Save the strings found in this request (new ones only). */ public function flush(): void { if ( ! $this->capture || ! $this->found ) { return; } $this->catalog->add( array_values( $this->found ) ); $this->found = array(); } /** @return array Strings found and not saved yet. */ public function found(): array { return array_values( $this->found ); } /** Tests: forget per-request state. */ public function reset(): void { $this->lang = null; $this->capture = null; $this->found = array(); $this->rules->reset(); } private function filter( mixed $translation, mixed $text, string $context, string $domain ): mixed { if ( $this->busy || ! is_string( $text ) || '' === $text ) { return $translation; } $this->busy = true; try { $lang = $this->active_lang(); if ( '' === $lang ) { return $translation; } $key = StringCatalog::key( $domain, $context, $text ); $map = $this->catalog->map( $lang ); if ( isset( $map[ $key ] ) ) { return $map[ $key ]; } if ( $this->capture && $translation === $text && did_action( 'template_redirect' ) && ! isset( $this->found[ $key ] ) && $this->rules->collectable( $text, $domain, $context ) ) { $this->found[ $key ] = array( $domain, $context, $text ); } return $translation; } finally { $this->busy = false; } } }