fix: translation too slow

This commit is contained in:
hanyixuanten
2026-07-27 00:33:29 +08:00
parent 69ac12b55c
commit 5f0d0c9790
5 changed files with 112 additions and 66 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ Before production use, test the plugin in a WordPress staging environment. Cover
The Posts and Pages lists show a status column for every target language, including the latest successful translation time and a translation action. The Settings page provides **重新翻译所有内容**, **翻译所有文章**, and **翻译所有分类标签**. Each action shows a confirmation before scheduling a forced refresh of all supported content, only published posts and pages, or only categories and tags; this bypasses persisted translations and consumes Baidu API quota.
The Settings page also shows a live progress panel. It refreshes every five seconds and counts completed post/page and category/tag items for every configured target language. When **重新翻译所有内容** starts a batch, progress resets and counts only translations completed after that batch began, so historical translations do not make a new batch appear complete. Forced retranslation jobs are scheduled one minute apart, so a single WP-Cron request processes at most one batch item instead of timing out while processing every post. Progress reads only local WordPress and translation-table data; it does not send additional requests to Baidu.
The Settings page also shows a live progress panel. It refreshes every five seconds and reports the latest translation task: full translation, post/page translation, category/tag translation, or a single-post translation. It counts only completed items that belong to that task's queued content and target languages after the task began, so historical translations cannot make a new task appear complete. Forced retranslation jobs begin immediately and are scheduled two seconds apart, so a single WP-Cron request processes at most one task item instead of timing out while processing every post. Progress reads only local WordPress and translation-table data; it does not send additional requests to Baidu.
The **清除已翻译的缓存** action deletes every persisted translation, cancels pending translation jobs, and resets batch progress. The front end then falls back to source content until translations are explicitly queued again; regenerating them consumes Baidu API quota.
+1 -1
View File
@@ -74,7 +74,7 @@ find . -path './.git' -prune -o -type f -name '*.php' -print0 | xargs -0 -n1 php
文章和页面列表会显示每种目标语言的状态列,包括最近成功翻译时间和翻译操作。设置页提供 **重新翻译所有内容**、**翻译所有文章** 和 **翻译所有分类标签**。它们会在为相应内容安排强制刷新前显示确认警告;此操作会绕过已持久化的翻译并消耗百度 API 配额。
设置页还显示实时进度面板。面板每五秒刷新一次,统计每种已配置目标语言中完成的文章/页面分类/标签项目。当 **重新翻译所有内容** 启动批处理时,进度会重置,仅统计该批处理开始后完成的翻译,因此历史翻译不会让新的批处理看似已完成。强制重翻任务会按一分钟间隔排队单次 WP-Cron 请求最多处理一个内容项,避免一次处理全部文章而超时。进度只读取本地 WordPress 和翻译表数据,不会发送额外请求。
设置页还显示实时进度面板。面板每五秒刷新一次,显示最近一次翻译任务的进度,覆盖全量翻译、文章页面翻译、分类标签翻译,以及单篇文章翻译。进度仅统计该任务启动后、且属于其已入队内容和目标语言的完成项目,因此历史翻译不会让新的任务看似已完成。强制重翻任务会立即开始,并按两秒间隔排队单次 WP-Cron 请求最多处理一个内容项,避免一次处理全部内容而超时。进度只读取本地 WordPress 和翻译表数据,不会发送额外请求。
设置页的 **清除已翻译的缓存** 会删除所有持久化翻译记录、取消待执行的翻译任务并重置进度。清除后前台将显示源文,需使用相应翻译操作重新生成翻译;重新生成会再次消耗百度 API 配额。
+72 -44
View File
@@ -120,7 +120,7 @@ class WPT_Admin {
var progress = response.data;
container.innerHTML = '<p><strong>' + progress.percent + '%</strong>' + progress.completed + ' / ' + progress.total + ' 个内容项)</p>' +
'<div style="max-width:480px;height:12px;background:#dcdcde"><div style="height:12px;width:' + progress.percent + '%;background:#2271b1"></div></div>' +
'<p class="description">文章和页面:' + progress.posts_completed + ' / ' + progress.posts_total + ';分类和标签:' + progress.terms_completed + ' / ' + progress.terms_total + '。显示最近一次全量重翻批次的进度,每 5 秒自动更新。</p>';
'<p class="description">最近任务:' + progress.task_label + '。文章和页面:' + progress.posts_completed + ' / ' + progress.posts_total + ';分类和标签:' + progress.terms_completed + ' / ' + progress.terms_total + '。每 5 秒自动更新。</p>';
})
.catch(function () {
container.innerHTML = '<p>暂时无法读取翻译进度。</p>';
@@ -146,17 +146,11 @@ class WPT_Admin {
wp_die( '无效的翻译范围。' );
}
$batch_started_at = current_time( 'mysql', true );
update_option(
'wpt_retranslation_batch',
array(
'started_at' => $batch_started_at,
),
false
);
$scheduled = 0;
$next_run = time() + MINUTE_IN_SECONDS;
$next_run = time();
$post_ids = array();
$term_ids = array();
$languages = WPT_Settings::target_languages();
if ( 'all' === $scope || 'posts' === $scope ) {
$post_ids = get_posts(
@@ -168,33 +162,38 @@ class WPT_Admin {
)
);
foreach ( $post_ids as $post_id ) {
foreach ( WPT_Settings::target_languages() as $target_language ) {
$this->clear_scheduled_event( 'wpt_process_translation', array( $post_id, $target_language, true ) );
wp_schedule_single_event( $next_run, 'wpt_process_translation', array( $post_id, $target_language, true ) );
$next_run += MINUTE_IN_SECONDS;
++$scheduled;
}
}
}
if ( 'all' === $scope || 'terms' === $scope ) {
$terms = get_terms(
$term_ids = get_terms(
array(
'taxonomy' => array( 'category', 'post_tag' ),
'hide_empty' => false,
'fields' => 'ids',
)
);
if ( ! is_wp_error( $terms ) ) {
foreach ( $terms as $term_id ) {
foreach ( WPT_Settings::target_languages() as $target_language ) {
$this->clear_scheduled_event( 'wpt_process_term_translation', array( $term_id, $target_language, true ) );
wp_schedule_single_event( $next_run, 'wpt_process_term_translation', array( $term_id, $target_language, true ) );
$next_run += MINUTE_IN_SECONDS;
++$scheduled;
}
}
if ( is_wp_error( $term_ids ) ) {
$term_ids = array();
}
}
$this->update_translation_task( $post_ids, $term_ids, $languages );
foreach ( $post_ids as $post_id ) {
foreach ( $languages as $target_language ) {
$this->clear_scheduled_event( 'wpt_process_translation', array( $post_id, $target_language, true ) );
wp_schedule_single_event( $next_run, 'wpt_process_translation', array( $post_id, $target_language, true ) );
$next_run += 2;
++$scheduled;
}
}
foreach ( $term_ids as $term_id ) {
foreach ( $languages as $target_language ) {
$this->clear_scheduled_event( 'wpt_process_term_translation', array( $term_id, $target_language, true ) );
wp_schedule_single_event( $next_run, 'wpt_process_term_translation', array( $term_id, $target_language, true ) );
$next_run += 2;
++$scheduled;
}
}
@@ -217,6 +216,7 @@ class WPT_Admin {
wp_clear_scheduled_hook( 'wpt_process_translation' );
wp_clear_scheduled_hook( 'wpt_process_term_translation' );
wp_clear_scheduled_hook( 'wpt_process_seo_output_translation' );
delete_option( 'wpt_translation_task' );
delete_option( 'wpt_retranslation_batch' );
wp_safe_redirect( add_query_arg( 'wpt_cache_cleared', 1, admin_url( 'options-general.php?page=wp-btranslate' ) ) );
@@ -242,22 +242,15 @@ class WPT_Admin {
wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
}
$post_count = (int) wp_count_posts( 'post' )->publish + (int) wp_count_posts( 'page' )->publish;
$terms = get_terms(
array(
'taxonomy' => array( 'category', 'post_tag' ),
'hide_empty' => false,
'fields' => 'ids',
)
);
$term_count = is_wp_error( $terms ) ? 0 : count( $terms );
$languages = WPT_Settings::target_languages();
$batch = (array) get_option( 'wpt_retranslation_batch', array() );
$started_at = isset( $batch['started_at'] ) ? sanitize_text_field( $batch['started_at'] ) : '';
$counts = ( new WPT_Translation_Store() )->get_completed_item_counts( $languages, $started_at );
$task = (array) get_option( 'wpt_translation_task', array() );
$post_ids = array_values( array_filter( array_map( 'absint', (array) ( $task['post_ids'] ?? array() ) ) ) );
$term_ids = array_values( array_filter( array_map( 'absint', (array) ( $task['term_ids'] ?? array() ) ) ) );
$languages = array_values( array_filter( array_map( 'sanitize_key', (array) ( $task['target_languages'] ?? array() ) ) ) );
$started_at = isset( $task['started_at'] ) ? sanitize_text_field( $task['started_at'] ) : '';
$counts = ( new WPT_Translation_Store() )->get_completed_item_counts( $languages, $post_ids, $term_ids, $started_at );
$posts_total = $post_count * count( $languages );
$terms_total = $term_count * count( $languages );
$posts_total = count( $post_ids ) * count( $languages );
$terms_total = count( $term_ids ) * count( $languages );
$total = $posts_total + $terms_total;
$posts_completed = min( $counts['posts'], $posts_total );
$terms_completed = min( $counts['terms'], $terms_total );
@@ -274,6 +267,7 @@ class WPT_Admin {
'total' => $total,
'percent' => $percent,
'batch_started_at' => $started_at,
'task_label' => $this->translation_task_label( $post_ids, $term_ids ),
)
);
}
@@ -327,9 +321,43 @@ class WPT_Admin {
}
check_admin_referer( 'wpt_translate_post_' . $post_id . '_' . $target_language );
$this->update_translation_task( array( $post_id ), array(), array( $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;
}
private function update_translation_task( $post_ids, $term_ids, $target_languages ) {
update_option(
'wpt_translation_task',
array(
'started_at' => current_time( 'mysql', true ),
'post_ids' => array_values( array_filter( array_map( 'absint', (array) $post_ids ) ) ),
'term_ids' => array_values( array_filter( array_map( 'absint', (array) $term_ids ) ) ),
'target_languages' => array_values( array_filter( array_map( 'sanitize_key', (array) $target_languages ) ) ),
),
false
);
}
private function translation_task_label( $post_ids, $term_ids ) {
if ( ! empty( $post_ids ) && ! empty( $term_ids ) ) {
return '全量翻译';
}
if ( 1 === count( $post_ids ) ) {
return '单篇文章翻译';
}
if ( ! empty( $post_ids ) ) {
return '文章和页面翻译';
}
if ( ! empty( $term_ids ) ) {
return '分类和标签翻译';
}
return '暂无翻译任务';
}
}
+37 -20
View File
@@ -93,10 +93,12 @@ class WPT_Translation_Store {
return $wpdb->get_row( $sql, ARRAY_A );
}
public function get_completed_item_counts( $target_languages, $since = '' ) {
public function get_completed_item_counts( $target_languages, $post_ids, $term_ids, $since = '' ) {
global $wpdb;
$target_languages = array_values( array_filter( array_map( 'sanitize_key', (array) $target_languages ) ) );
$post_ids = array_values( array_filter( array_map( 'absint', (array) $post_ids ) ) );
$term_ids = array_values( array_filter( array_map( 'absint', (array) $term_ids ) ) );
if ( empty( $target_languages ) ) {
return array(
'posts' => 0,
@@ -104,29 +106,44 @@ class WPT_Translation_Store {
);
}
$table_name = self::table_name();
$placeholders = implode( ',', array_fill( 0, count( $target_languages ), '%s' ) );
$since_clause = '' === $since ? '' : ' AND updated_at >= %s';
$query_args = $target_languages;
if ( '' !== $since ) {
$query_args[] = $since;
}
$post_sql = $wpdb->prepare(
"SELECT COUNT(DISTINCT CONCAT(target_language, '|', SUBSTRING_INDEX(field_context, ':', 2)))
FROM {$table_name}
WHERE status = 'complete' AND target_language IN ({$placeholders}) AND field_context LIKE 'post:%'{$since_clause}",
$query_args
);
$term_sql = $wpdb->prepare(
"SELECT COUNT(DISTINCT CONCAT(target_language, '|', SUBSTRING_INDEX(field_context, ':', 2)))
FROM {$table_name}
WHERE status = 'complete' AND target_language IN ({$placeholders}) AND field_context LIKE 'term:%'{$since_clause}",
$query_args
);
$table_name = self::table_name();
$language_placeholders = implode( ',', array_fill( 0, count( $target_languages ), '%s' ) );
$since_clause = '' === $since ? '' : ' AND updated_at >= %s';
$post_sql = $this->get_completed_item_count_sql( 'post', $post_ids, $target_languages, $language_placeholders, $since, $since_clause, $table_name );
$term_sql = $this->get_completed_item_count_sql( 'term', $term_ids, $target_languages, $language_placeholders, $since, $since_clause, $table_name );
return array(
'posts' => (int) $wpdb->get_var( $post_sql ),
'terms' => (int) $wpdb->get_var( $term_sql ),
);
}
private function get_completed_item_count_sql( $item_type, $item_ids, $target_languages, $language_placeholders, $since, $since_clause, $table_name ) {
global $wpdb;
if ( empty( $item_ids ) ) {
return 'SELECT 0';
}
$contexts = array_map(
static function ( $item_id ) use ( $item_type ) {
return $item_type . ':' . $item_id;
},
$item_ids
);
$context_placeholders = implode( ',', array_fill( 0, count( $contexts ), '%s' ) );
$query_args = array_merge( $target_languages, $contexts );
if ( '' !== $since ) {
$query_args[] = $since;
}
return $wpdb->prepare(
"SELECT COUNT(DISTINCT CONCAT(target_language, '|', SUBSTRING_INDEX(field_context, ':', 2)))
FROM {$table_name}
WHERE status = 'complete'
AND target_language IN ({$language_placeholders})
AND SUBSTRING_INDEX(field_context, ':', 2) IN ({$context_placeholders}){$since_clause}",
$query_args
);
}
}
+1
View File
@@ -16,6 +16,7 @@ function wpt_uninstall_site_data() {
wp_clear_scheduled_hook( 'wpt_process_term_translation' );
wp_clear_scheduled_hook( 'wpt_process_seo_output_translation' );
delete_option( 'wpt_settings' );
delete_option( 'wpt_translation_task' );
delete_option( 'wpt_retranslation_batch' );
$table_name = $wpdb->prefix . 'wpt_translations';