Translations, * group "Abientot") and translated only where visitors see them. Admin screens and * saved records always keep the original text. Without WPML or Polylang every * method returns its input unchanged. */ final class MultilingualText { public const CONTEXT = 'Abientot'; private const FINGERPRINT = 'abientot_wpml_strings_fingerprint'; /** Free-text form settings visitors read. */ private const FORM_TEXT = array( 'form_title', 'form_intro', 'form_button_label', 'policy_checkbox_text', 'confirmation_message', 'payment_instructions' ); public static function register_hooks(): void { add_action( 'admin_init', array( self::class, 'register_strings' ), 20 ); } private static function wpml(): bool { return defined( 'ICL_SITEPRESS_VERSION' ); } private static function polylang(): bool { return ! self::wpml() && function_exists( 'pll__' ) && function_exists( 'pll_register_string' ); } public static function active(): bool { return self::wpml() || self::polylang(); } /** Current visitor language code for AJAX requests (both plugins read a "lang" request field), or ''. */ public static function current_language(): string { if ( self::polylang() && function_exists( 'pll_current_language' ) ) { $language = pll_current_language(); } elseif ( self::wpml() ) { $language = apply_filters( 'wpml_current_language', null ); } else { return ''; } return is_string( $language ) ? sanitize_key( $language ) : ''; } public static function translate( string $value, string $name ): string { if ( '' === trim( $value ) || ! self::active() ) { return $value; } $translated = self::polylang() ? pll__( $value ) : apply_filters( 'wpml_translate_single_string', $value, self::CONTEXT, $name ); return is_string( $translated ) && '' !== $translated ? $translated : $value; } /** * @param array $service Service row, optionally joined with its category. * @return array */ public static function service( array $service ): array { if ( ! self::active() ) { return $service; } $id = (int) ( $service['id'] ?? 0 ); foreach ( array( 'name', 'description' ) as $field ) { if ( isset( $service[ $field ] ) ) { $service[ $field ] = self::translate( (string) $service[ $field ], 'service-' . $id . '-' . $field ); } } $category = (int) ( $service['category_id'] ?? 0 ); foreach ( array( 'name', 'description' ) as $field ) { if ( $category > 0 && isset( $service[ 'category_' . $field ] ) ) { $service[ 'category_' . $field ] = self::translate( (string) $service[ 'category_' . $field ], 'category-' . $category . '-' . $field ); } } return $service; } /** * @param array $location Location row. * @return array */ public static function location( array $location ): array { if ( self::active() && isset( $location['name'] ) ) { $location['name'] = self::translate( (string) $location['name'], 'location-' . (int) ( $location['id'] ?? 0 ) . '-name' ); } return $location; } /** * @param array $settings Booking form settings. * @return array */ public static function form_settings( array $settings, int $form_id ): array { if ( ! self::active() ) { return $settings; } $main = $form_id > 0 ? BookingForms::settings( 0 ) : $settings; foreach ( self::FORM_TEXT as $key ) { if ( isset( $settings[ $key ] ) && is_string( $settings[ $key ] ) ) { $settings[ $key ] = self::translate( $settings[ $key ], self::form_string_name( $form_id, $key, $settings[ $key ], $main ) ); } } return $settings; } /** A form that keeps the main form's text shares its string, so it is translated once. */ private static function form_string_name( int $form_id, string $key, string $value, array $main ): string { return 'form-' . ( $form_id > 0 && $value === ( $main[ $key ] ?? null ) ? 0 : $form_id ) . '-' . $key; } /** * Every translatable string, keyed by its stable name. * * @return array */ public static function strings(): array { $strings = array(); $add = static function ( string $name, $value, bool $multiline = false ) use ( &$strings ): void { if ( is_string( $value ) && '' !== trim( $value ) ) { $strings[ $name ] = array( $value, $multiline ); } }; foreach ( ( new ServicesRepository() )->list_for_admin() as $service ) { if ( 'archived' === (string) ( $service['status'] ?? '' ) ) { continue; } $id = (int) $service['id']; $add( 'service-' . $id . '-name', $service['name'] ?? '' ); $add( 'service-' . $id . '-description', $service['description'] ?? '', true ); $category = (int) ( $service['category_id'] ?? 0 ); if ( $category > 0 ) { $add( 'category-' . $category . '-name', $service['category_name'] ?? '' ); $add( 'category-' . $category . '-description', $service['category_description'] ?? '', true ); } } $location = ( new LocationsRepository() )->default_location(); if ( $location ) { $add( 'location-' . (int) $location['id'] . '-name', $location['name'] ?? '' ); } $main = BookingForms::settings( 0 ); foreach ( array_keys( BookingForms::names() ) as $form_id ) { $settings = BookingForms::settings( (int) $form_id ); foreach ( self::FORM_TEXT as $key ) { $value = is_string( $settings[ $key ] ?? null ) ? $settings[ $key ] : ''; $add( self::form_string_name( (int) $form_id, $key, $value, $main ), $value, in_array( $key, array( 'form_intro', 'confirmation_message', 'payment_instructions' ), true ) ); } } return (array) apply_filters( 'abientot_translatable_strings', $strings ); } /** * Polylang keeps registered strings for the current request only, so they are registered on * each admin load. WPML stores them, so they are sent again only when the text changed. */ public static function register_strings(): void { if ( ! self::active() || ! current_user_can( 'abientot_manage_settings' ) ) { return; } try { $strings = self::strings(); } catch ( \Throwable $error ) { return; } if ( self::polylang() ) { foreach ( $strings as $name => $string ) { pll_register_string( $name, $string[0], self::CONTEXT, $string[1] ); } return; } $fingerprint = md5( (string) wp_json_encode( $strings ) ); if ( get_option( self::FINGERPRINT ) === $fingerprint ) { return; } foreach ( $strings as $name => $string ) { do_action( 'wpml_register_single_string', self::CONTEXT, $name, $string[0] ); } update_option( self::FINGERPRINT, $fingerprint, false ); } }