Language strings: list, edit and delete the collected theme * and plugin strings. Scanning and machine translation run in the browser * through Rest\StringsController. */ declare( strict_types = 1 ); namespace AddBlack\Lang\Admin; defined( 'ABSPATH' ) || exit; use AddBlack\Lang\Hookable; use AddBlack\Lang\Hooks; use AddBlack\Lang\LanguagePacks\LanguagePackInstaller; use AddBlack\Lang\Settings\SettingsRepository; use AddBlack\Lang\Strings\StringCatalog; final class StringsPage implements Hookable { public const SLUG = 'blocklingo-strings'; public const PER_PAGE = 50; public function __construct( private readonly SettingsRepository $settings, private readonly StringCatalog $strings, private readonly AdminAssets $assets, private readonly View $view, private readonly LanguagePackInstaller $language_packs ) {} public function register(): void { Hooks::action( 'admin_menu', array( $this, 'menu' ), 11 ); Hooks::action( 'admin_post_blocklingo_save_strings', array( $this, 'save' ) ); Hooks::action( 'admin_post_blocklingo_install_langpacks', array( $this, 'install_langpacks' ) ); } /** @param array $args */ public static function url( array $args = array() ): string { return add_query_arg( array_merge( array( 'page' => self::SLUG ), $args ), admin_url( 'options-general.php' ) ); } public function menu(): void { $hook = add_options_page( __( 'Language strings', 'blocklingo' ), __( 'Language strings', 'blocklingo' ), 'manage_options', self::SLUG, array( $this, 'render' ) ); if ( $hook ) { Hooks::action( 'admin_print_styles-' . $hook, array( $this->assets, 'styles' ) ); Hooks::action( 'admin_print_scripts-' . $hook, array( $this, 'scripts' ) ); } } public function scripts(): void { $this->assets->strings_scripts( $this->mt_ready() ); } public function render(): void { $filters = $this->filters(); $result = $this->strings->query( $filters ); $settings = $this->settings->get(); $labels = array(); foreach ( $settings->languages() as $code => $lang ) { $labels[ $code ] = $lang->label(); } $kept = array_filter( array( 'domain' => $filters['domain'], 'status' => $filters['status'], 's' => $filters['search'], ) ); $this->view->render( 'strings', array( 'filters' => $filters, 'items' => $result['items'], 'total' => $result['total'], 'pages' => (int) ceil( $result['total'] / self::PER_PAGE ), 'targets' => $this->strings->target_langs(), 'domains' => $this->strings->domains(), 'labels' => $labels, 'source' => $labels[ $settings->default_code() ] ?? '', 'mt_ready' => $this->mt_ready(), 'back' => self::url( array_merge( $kept, $filters['page'] > 1 ? array( 'paged' => $filters['page'] ) : array() ) ), 'paginate' => add_query_arg( 'paged', '%#%', self::url( $kept ) ), 'notices' => $this->notices(), 'missing' => $this->language_packs->missing(), ) ); } public function save(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to do that.', 'blocklingo' ), 403 ); } check_admin_referer( 'blocklingo_save_strings' ); $back = isset( $_POST['back'] ) ? esc_url_raw( wp_unslash( $_POST['back'] ) ) : self::url(); $back = remove_query_arg( array( 'saved', 'deleted' ), $back ); if ( isset( $_POST['do'] ) && 'delete' === $_POST['do'] ) { $ids = isset( $_POST['delete'] ) ? array_map( 'intval', (array) wp_unslash( $_POST['delete'] ) ) : array(); wp_safe_redirect( add_query_arg( 'deleted', $this->strings->delete( $ids ), $back ) ); exit; } $this->apply( isset( $_POST['tr'] ) ? (array) map_deep( wp_unslash( $_POST['tr'] ), 'wp_kses_post' ) : array() ); wp_safe_redirect( add_query_arg( 'saved', 1, $back ) ); exit; } /** "Install now" on the WordPress translation files card. */ public function install_langpacks(): void { if ( ! current_user_can( 'manage_options' ) || ! current_user_can( 'install_languages' ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to do that.', 'blocklingo' ), 403 ); } check_admin_referer( 'blocklingo_install_langpacks' ); wp_safe_redirect( $this->install_langpacks_url() ); exit; } /** Install the files for every language; this screen with the result. */ public function install_langpacks_url(): string { $installed = $this->language_packs->install(); $args = array( 'blocklingo_langpacks' => rawurlencode( LanguagePackInstaller::summary( $installed ) ) ); if ( is_wp_error( $installed ) || ! empty( $installed['unreachable'] ) ) { $args['blocklingo_langpacks_failed'] = 1; } return self::url( $args ); } /** * Save posted translations: [ id => [ lang => value ] ]. Unknown languages are ignored. * * @param array $translations */ public function apply( array $translations ): void { $targets = $this->strings->target_langs(); foreach ( $translations as $id => $values ) { foreach ( (array) $values as $lang => $value ) { if ( in_array( $lang, $targets, true ) ) { $this->strings->set_translation( (int) $id, (string) $lang, wp_kses_post( trim( (string) $value ) ) ); } } } } private function mt_ready(): bool { return $this->settings->get()->machine_ready(); } /** * Filters from the query string. * * @return array{domain: string, search: string, status: string, page: int, per_page: int} */ private function filters(): array { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only filters. $out = array( 'domain' => isset( $_GET['domain'] ) ? sanitize_text_field( wp_unslash( $_GET['domain'] ) ) : '', 'search' => isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '', 'status' => isset( $_GET['status'] ) && 'untranslated' === $_GET['status'] ? 'untranslated' : '', 'page' => isset( $_GET['paged'] ) ? max( 1, (int) $_GET['paged'] ) : 1, 'per_page' => self::PER_PAGE, ); // phpcs:enable return $out; } /** @return array{saved: bool, deleted: int|null, langpacks: string, langpacks_failed: bool} */ private function notices(): array { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- display only. $out = array( 'saved' => isset( $_GET['saved'] ), 'deleted' => isset( $_GET['deleted'] ) ? (int) $_GET['deleted'] : null, 'langpacks' => isset( $_GET['blocklingo_langpacks'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['blocklingo_langpacks'] ) ) : '', 'langpacks_failed' => isset( $_GET['blocklingo_langpacks_failed'] ), ); // phpcs:enable return $out; } }