feat: Display translated/untranslated status, last translation time, retranslate (if translated) and translate (if not translated) buttons for each interface in the backend

This commit is contained in:
hanyixuanten
2026-07-26 00:09:12 +08:00
parent b8b5ee6287
commit 58599c92ec
5 changed files with 101 additions and 12 deletions
+60
View File
@@ -7,6 +7,11 @@ class WPT_Admin {
add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_post_wpt_queue_existing_translations', array( $this, 'queue_existing_translations' ) );
add_action( 'admin_post_wpt_translate_post', array( $this, 'queue_post_translation' ) );
add_filter( 'manage_post_posts_columns', array( $this, 'add_translation_column' ) );
add_filter( 'manage_page_posts_columns', array( $this, 'add_translation_column' ) );
add_action( 'manage_post_posts_custom_column', array( $this, 'render_translation_column' ), 10, 2 );
add_action( 'manage_page_posts_custom_column', array( $this, 'render_translation_column' ), 10, 2 );
}
public function add_settings_page() {
@@ -103,4 +108,59 @@ class WPT_Admin {
wp_safe_redirect( add_query_arg( 'wpt_queued', $scheduled, admin_url( 'options-general.php?page=wp-translate' ) ) );
exit;
}
public function add_translation_column( $columns ) {
$columns['wpt_translation'] = '翻译状态';
return $columns;
}
public function render_translation_column( $column, $post_id ) {
if ( 'wpt_translation' !== $column ) {
return;
}
$store = new WPT_Translation_Store();
foreach ( WPT_Settings::target_languages() as $target_language ) {
$status = $store->get_post_language_status( $post_id, $target_language );
$translated = ! empty( $status['translated_fields'] );
$button_text = $translated ? '重新翻译' : '翻译';
$action_url = wp_nonce_url(
add_query_arg(
array(
'action' => 'wpt_translate_post',
'post_id' => $post_id,
'language' => $target_language,
'force' => $translated ? '1' : '0',
),
admin_url( 'admin-post.php' )
),
'wpt_translate_post_' . $post_id . '_' . $target_language
);
echo '<p><strong>' . esc_html( strtoupper( $target_language ) ) . '</strong>: ';
echo $translated ? '<span style="color:#008a20">已翻译</span>' : '<span>未翻译</span>';
if ( $translated && ! empty( $status['last_translated_at'] ) ) {
echo '<br><small>上次翻译:' . esc_html( get_date_from_gmt( $status['last_translated_at'], 'Y-m-d H:i' ) ) . '</small>';
}
echo '<br><a class="button button-small" href="' . esc_url( $action_url ) . '">' . esc_html( $button_text ) . '</a></p>';
}
}
public function queue_post_translation() {
$post_id = isset( $_GET['post_id'] ) ? absint( $_GET['post_id'] ) : 0;
$target_language = isset( $_GET['language'] ) ? sanitize_key( wp_unslash( $_GET['language'] ) ) : '';
$force_refresh = ! empty( $_GET['force'] );
if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) || ! WPT_Settings::is_supported_language( $target_language ) || $target_language === WPT_Settings::get()['source_language'] ) {
wp_die( '无效的翻译请求。' );
}
check_admin_referer( 'wpt_translate_post_' . $post_id . '_' . $target_language );
wp_schedule_single_event( time() + 5, 'wpt_process_translation', array( $post_id, $target_language, $force_refresh ) );
wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'edit.php?post_type=' . get_post_type( $post_id ) ) );
exit;
}
}
+10 -10
View File
@@ -13,7 +13,7 @@ class WPT_Content_Controller {
public function register() {
add_action( 'save_post', array( $this, 'schedule_post_translation' ), 20, 3 );
add_action( 'wpt_process_translation', array( $this, 'translate_post' ), 10, 2 );
add_action( 'wpt_process_translation', array( $this, 'translate_post' ), 10, 3 );
add_filter( 'the_title', array( $this, 'translate_title' ), 20, 2 );
add_filter( 'the_content', array( $this, 'translate_content' ), 20 );
add_filter( 'get_the_excerpt', array( $this, 'translate_excerpt' ), 20, 2 );
@@ -42,7 +42,7 @@ class WPT_Content_Controller {
}
}
public function translate_post( $post_id, $target_language ) {
public function translate_post( $post_id, $target_language, $force_refresh = false ) {
$post = get_post( $post_id );
$settings = WPT_Settings::get();
@@ -76,12 +76,12 @@ class WPT_Content_Controller {
foreach ( $fields as $field => $value ) {
if ( '' !== $value ) {
$service->get_or_translate( $value, $settings['source_language'], $target_language, 'post:' . $post_id . ':' . $field );
$service->get_or_translate( $value, $settings['source_language'], $target_language, 'post:' . $post_id . ':' . $field, $force_refresh );
}
}
$this->translate_attachment_alt_text( $post_id, $service, $settings, $target_language );
$this->translate_terms( $post_id, $service, $settings, $target_language );
$this->translate_attachment_alt_text( $post_id, $service, $settings, $target_language, $force_refresh );
$this->translate_terms( $post_id, $service, $settings, $target_language, $force_refresh );
}
public function translate_title( $title, $post_id ) {
@@ -156,18 +156,18 @@ class WPT_Content_Controller {
return ! empty( $translation['translated_value'] ) ? $translation['translated_value'] : $source_value;
}
private function translate_attachment_alt_text( $post_id, $service, $settings, $target_language ) {
private function translate_attachment_alt_text( $post_id, $service, $settings, $target_language, $force_refresh ) {
$attachments = get_attached_media( 'image', $post_id );
foreach ( $attachments as $attachment ) {
$alt_text = get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true );
if ( is_string( $alt_text ) && '' !== $alt_text ) {
$service->get_or_translate( $alt_text, $settings['source_language'], $target_language, 'attachment:' . $attachment->ID . ':alt' );
$service->get_or_translate( $alt_text, $settings['source_language'], $target_language, 'attachment:' . $attachment->ID . ':alt', $force_refresh );
}
}
}
private function translate_terms( $post_id, $service, $settings, $target_language ) {
private function translate_terms( $post_id, $service, $settings, $target_language, $force_refresh ) {
$terms = get_the_terms( $post_id, array( 'category', 'post_tag' ) );
if ( ! is_array( $terms ) ) {
@@ -176,10 +176,10 @@ class WPT_Content_Controller {
foreach ( $terms as $term ) {
if ( '' !== $term->name ) {
$service->get_or_translate( $term->name, $settings['source_language'], $target_language, 'term:' . $term->term_id . ':name' );
$service->get_or_translate( $term->name, $settings['source_language'], $target_language, 'term:' . $term->term_id . ':name', $force_refresh );
}
if ( '' !== $term->description ) {
$service->get_or_translate( $term->description, $settings['source_language'], $target_language, 'term:' . $term->term_id . ':description' );
$service->get_or_translate( $term->description, $settings['source_language'], $target_language, 'term:' . $term->term_id . ':description', $force_refresh );
}
}
}
+2 -2
View File
@@ -11,11 +11,11 @@ class WPT_Translation_Service {
$this->provider = $provider;
}
public function get_or_translate( $source_value, $source_language, $target_language, $context ) {
public function get_or_translate( $source_value, $source_language, $target_language, $context, $force_refresh = false ) {
$source_value = (string) $source_value;
$source_fingerprint = WPT_Translation_Identity::source_fingerprint( $source_value );
$identity_key = WPT_Translation_Identity::key( $source_value, $source_language, $target_language, $context, $this->provider->get_version() );
$existing = $this->store->find_valid( $identity_key );
$existing = $force_refresh ? null : $this->store->find_valid( $identity_key );
if ( ! empty( $existing['translated_value'] ) ) {
return WPT_Translation_Result::success( $existing['translated_value'] );
+17
View File
@@ -69,4 +69,21 @@ class WPT_Translation_Store {
array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
);
}
public function get_post_language_status( $post_id, $target_language ) {
global $wpdb;
$table_name = self::table_name();
$sql = $wpdb->prepare(
"SELECT COUNT(*) AS translated_fields, MAX(updated_at) AS last_translated_at
FROM {$table_name}
WHERE target_language = %s
AND status = 'complete'
AND field_context LIKE %s",
$target_language,
'post:' . absint( $post_id ) . ':%'
);
return $wpdb->get_row( $sql, ARRAY_A );
}
}
+12
View File
@@ -65,4 +65,16 @@ class TranslationServiceTest extends TestCase {
$this->assertFalse( $result->success );
$this->assertSame( 'failed', $store->save_calls[0]['status'] );
}
public function test_forced_refresh_calls_the_provider_when_a_translation_is_already_persisted() {
$store = new WPT_Test_Translation_Store();
$provider = new WPT_Test_Translation_Provider();
$service = new WPT_Translation_Service( $store, $provider );
$service->get_or_translate( 'Welcome', 'en', 'zh', 'post:8:post_title' );
$service->get_or_translate( 'Welcome', 'en', 'zh', 'post:8:post_title', true );
$this->assertSame( 2, $provider->calls );
$this->assertCount( 2, $store->save_calls );
}
}