settings = $settings; $this->translator = $translator; } /** * Registers the command with WP-CLI. * * @since 1.0.0 * * @return void */ public function register() { if ( ! class_exists( 'WP_CLI' ) ) { return; } WP_CLI::add_command( 'ai-translator', $this ); } /** * Translates one or more posts to a target locale. * * ## OPTIONS * * --locale= * : WordPress locale to translate to. Must be installed on the site. * * [--post-id=] * : ID of a single post to translate. Mutually exclusive with --post-type. * * [--post-type=] * : Post type to translate in bulk. Defaults to 'post' when --post-id is omitted. * * [--status=] * : Post status filter when running in bulk mode. Default: 'publish'. * * [--limit=] * : Maximum number of posts to process when running in bulk mode. Default: 50. * * [--dry-run] * : Show what would be translated without calling the AI provider and without writing to the database. * * ## EXAMPLES * * wp ai-translator translate --post-id=42 --locale=es_ES * wp ai-translator translate --post-type=page --locale=fr_FR --limit=10 --dry-run * * @since 1.0.0 * * @param array $args Positional arguments (unused). * @param array $assoc_args Associative arguments. * * @return void */ public function translate( $args, $assoc_args ) { unset( $args ); $locale = isset( $assoc_args['locale'] ) ? (string) $assoc_args['locale'] : ''; $dry_run = isset( $assoc_args['dry-run'] ); $post_id = isset( $assoc_args['post-id'] ) ? (int) $assoc_args['post-id'] : 0; $type = isset( $assoc_args['post-type'] ) ? (string) $assoc_args['post-type'] : ''; $status = isset( $assoc_args['status'] ) ? (string) $assoc_args['status'] : 'publish'; $limit = isset( $assoc_args['limit'] ) ? max( 1, (int) $assoc_args['limit'] ) : 50; if ( '' === $locale ) { WP_CLI::error( __( 'The --locale flag is required.', 'robotstxt-ai-translator' ) ); } if ( $post_id > 0 && '' !== $type ) { WP_CLI::error( __( 'The --post-id and --post-type flags are mutually exclusive.', 'robotstxt-ai-translator' ) ); } // Validate locale before AI checks so the user sees the right error when // both the locale is missing and the AI provider is unconfigured. if ( ! in_array( $locale, $this->translator->get_installed_locales(), true ) ) { WP_CLI::error( sprintf( /* translators: %s: locale code. */ __( 'The locale %s is not installed on this site.', 'robotstxt-ai-translator' ), $locale ) ); } $current_user = wp_get_current_user(); if ( ! $current_user || 0 === (int) $current_user->ID ) { WP_CLI::warning( __( 'No user context. Pass --user= so post updates carry an author and capability checks apply.', 'robotstxt-ai-translator' ) ); } if ( ! $dry_run && ! $this->translator->is_available() ) { WP_CLI::error( __( 'AI features are disabled for this WordPress installation.', 'robotstxt-ai-translator' ) ); } if ( ! $dry_run && ! $this->translator->is_supported() ) { WP_CLI::error( __( 'No AI provider is configured for text generation. Open Settings → AI to set one up.', 'robotstxt-ai-translator' ) ); } $settings = $this->settings->get_settings(); if ( empty( $settings['translate_title'] ) && empty( $settings['translate_content'] ) && empty( $settings['translate_excerpt'] ) ) { WP_CLI::error( __( 'No translation fields are enabled in the plugin settings.', 'robotstxt-ai-translator' ) ); } if ( $post_id > 0 ) { $posts = array( get_post( $post_id ) ); } else { $type = '' !== $type ? $type : 'post'; $posts = get_posts( array( 'post_type' => $type, 'post_status' => $status, 'numberposts' => $limit, 'ignore_sticky_posts' => true, 'suppress_filters' => false, ) ); } $posts = array_filter( $posts, static function ( $post ) { return $post instanceof WP_Post; } ); if ( empty( $posts ) ) { WP_CLI::warning( __( 'No posts matched the given criteria.', 'robotstxt-ai-translator' ) ); return; } $successes = 0; $failures = 0; foreach ( $posts as $post ) { if ( $dry_run ) { WP_CLI::log( sprintf( '[dry-run] would translate post #%d (%s) to %s.', $post->ID, $post->post_title, $locale ) ); ++$successes; continue; } $update = array( 'ID' => $post->ID ); if ( ! empty( $settings['translate_title'] ) ) { $translated_title = $this->translator->translate( $post->post_title, $locale ); if ( is_wp_error( $translated_title ) ) { WP_CLI::warning( sprintf( '#%d title: %s', $post->ID, $translated_title->get_error_message() ) ); ++$failures; continue; } $update['post_title'] = $translated_title; } if ( ! empty( $settings['translate_content'] ) ) { $translated_content = $this->translator->translate( $post->post_content, $locale ); if ( is_wp_error( $translated_content ) ) { WP_CLI::warning( sprintf( '#%d content: %s', $post->ID, $translated_content->get_error_message() ) ); ++$failures; continue; } $update['post_content'] = $translated_content; } if ( ! empty( $settings['translate_excerpt'] ) && post_type_supports( $post->post_type, 'excerpt' ) ) { $translated_excerpt = $this->translator->translate( $post->post_excerpt, $locale ); if ( is_wp_error( $translated_excerpt ) ) { WP_CLI::warning( sprintf( '#%d excerpt: %s', $post->ID, $translated_excerpt->get_error_message() ) ); ++$failures; continue; } $update['post_excerpt'] = $translated_excerpt; } $result = wp_update_post( $update, true ); if ( is_wp_error( $result ) ) { WP_CLI::warning( sprintf( '#%d save: %s', $post->ID, $result->get_error_message() ) ); ++$failures; continue; } WP_CLI::log( sprintf( 'Updated post #%d.', $post->ID ) ); ++$successes; } WP_CLI::success( sprintf( /* translators: 1: success count, 2: failure count, 3: locale. */ __( '%1$d translated, %2$d failed (locale: %3$s).', 'robotstxt-ai-translator' ), $successes, $failures, $locale ) ); } } }