fix: category translate

fix: wrong src of the back button
feat: retranslate all
This commit is contained in:
hanyixuanten
2026-07-26 00:19:05 +08:00
parent 58599c92ec
commit 146b017ed7
4 changed files with 97 additions and 7 deletions
+8
View File
@@ -33,10 +33,14 @@ Changing source text produces a new fingerprint and therefore schedules a new tr
Only the listed SEO meta fields are sent for translation. Arbitrary metadata, serialized data, credentials, and code are excluded.
Category and tag names are translated when they are created, edited, or included in a full retranslation. On a translated site, term objects used by archives, navigation, and post metadata use the persisted translated name and description.
## Routing
The **Subdirectory** mode resolves configured language paths such as `/en/example-post/`. The **Domain binding** mode maps a request host to a language using one `domain=language` entry per line in Settings. Use one canonical strategy for each language to avoid duplicate URLs. In domain mode, the plugin does not register front-end rewrite rules, so the primary site URL is not affected.
Post, page, term, and home links are generated for the active language. For example, a theme's "back to home" link that uses `home_url()` resolves to the bound language domain or language subdirectory.
For a bound subdomain, enter a value such as `en.example.com=en`. The server and DNS must route that hostname to the same WordPress installation before WordPress can resolve the language.
After changing routing settings, rewrite rules are refreshed. The initial implementation does not yet generate translated permalinks, canonical URLs, `hreflang` alternates, redirects, or sitemap entries.
@@ -65,6 +69,10 @@ find . -path './.git' -prune -o -type f -name '*.php' -print0 | xargs -0 -n1 php
Before production use, add WordPress PHPUnit integration tests with mocked `wp_remote_post` responses. Required coverage includes language resolution, rewrite behavior, persistence reuse and invalidation, Baidu request signing and errors, content filtering, and fallback rendering.
## Retranslation
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 also provides **重新翻译所有内容**. It shows a confirmation warning before scheduling a forced refresh of published posts, pages, categories, and tags; this bypasses persisted translations and consumes Baidu API quota.
## Known Limitations
- The settings UI accepts language codes but does not yet validate them against a maintained Baidu language-code list.
+31 -6
View File
@@ -12,6 +12,7 @@ class WPT_Admin {
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 );
add_filter( 'plugin_action_links_' . plugin_basename( WPT_FILE ), array( $this, 'add_plugin_settings_link' ) );
}
public function add_settings_page() {
@@ -68,12 +69,12 @@ class WPT_Admin {
<?php submit_button(); ?>
</form>
<hr />
<h2>翻译有内容</h2>
<p>保存语言或百度凭据后,使用此操作将所有已发布文章页面加入翻译队列。</p>
<form action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="post">
<h2>重新翻译有内容</h2>
<p>此操作将所有已发布文章页面、分类和标签重新加入翻译队列。</p>
<form action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="post" onsubmit="return window.confirm('这将重新翻译所有内容,请注意 API 消耗。确认继续吗?');">
<input type="hidden" name="action" value="wpt_queue_existing_translations" />
<?php wp_nonce_field( 'wpt_queue_existing_translations' ); ?>
<?php submit_button( '翻译已有文章和页面', 'secondary', 'submit', false ); ?>
<?php submit_button( '重新翻译所有内容', 'secondary', 'submit', false ); ?>
</form>
</div>
<?php
@@ -98,17 +99,41 @@ class WPT_Admin {
foreach ( $post_ids as $post_id ) {
foreach ( WPT_Settings::target_languages() as $target_language ) {
if ( ! wp_next_scheduled( 'wpt_process_translation', array( $post_id, $target_language ) ) ) {
wp_schedule_single_event( time() + 30, 'wpt_process_translation', array( $post_id, $target_language ) );
if ( ! wp_next_scheduled( 'wpt_process_translation', array( $post_id, $target_language, true ) ) ) {
wp_schedule_single_event( time() + 30, 'wpt_process_translation', array( $post_id, $target_language, true ) );
++$scheduled;
}
}
}
$terms = 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 ) {
if ( ! wp_next_scheduled( 'wpt_process_term_translation', array( $term_id, $target_language, true ) ) ) {
wp_schedule_single_event( time() + 30, 'wpt_process_term_translation', array( $term_id, $target_language, true ) );
++$scheduled;
}
}
}
}
wp_safe_redirect( add_query_arg( 'wpt_queued', $scheduled, admin_url( 'options-general.php?page=wp-translate' ) ) );
exit;
}
public function add_plugin_settings_link( $links ) {
array_unshift( $links, '<a href="' . esc_url( admin_url( 'options-general.php?page=wp-translate' ) ) . '">设置</a>' );
return $links;
}
public function add_translation_column( $columns ) {
$columns['wpt_translation'] = '翻译状态';
+49
View File
@@ -14,6 +14,9 @@ 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, 3 );
add_action( 'wpt_process_term_translation', array( $this, 'translate_term' ), 10, 3 );
add_action( 'created_term', array( $this, 'schedule_term_translation' ), 20, 3 );
add_action( 'edited_term', array( $this, 'schedule_term_translation' ), 20, 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 );
@@ -28,6 +31,7 @@ class WPT_Content_Controller {
add_filter( 'page_link', array( $this, 'localize_permalink' ), 20 );
add_filter( 'post_type_link', array( $this, 'localize_permalink' ), 20 );
add_filter( 'term_link', array( $this, 'localize_permalink' ), 20 );
add_filter( 'get_term', array( $this, 'translate_term_object' ), 20, 3 );
}
public function schedule_post_translation( $post_id, $post, $update ) {
@@ -84,6 +88,39 @@ class WPT_Content_Controller {
$this->translate_terms( $post_id, $service, $settings, $target_language, $force_refresh );
}
public function schedule_term_translation( $term_id, $term_taxonomy_id, $taxonomy ) {
if ( ! in_array( $taxonomy, array( 'category', 'post_tag' ), true ) ) {
return;
}
foreach ( WPT_Settings::target_languages() as $target_language ) {
if ( ! wp_next_scheduled( 'wpt_process_term_translation', array( $term_id, $target_language, false ) ) ) {
wp_schedule_single_event( time() + 30, 'wpt_process_term_translation', array( $term_id, $target_language, false ) );
}
}
}
public function translate_term( $term_id, $target_language, $force_refresh = false ) {
$term = get_term( $term_id );
$settings = WPT_Settings::get();
if ( ! $term || is_wp_error( $term ) || ! in_array( $term->taxonomy, array( 'category', 'post_tag' ), true ) || ! WPT_Settings::is_supported_language( $target_language ) ) {
return;
}
$service = new WPT_Translation_Service(
$this->store,
new WPT_Baidu_Provider( $settings['baidu_app_id'], $settings['baidu_secret_key'] )
);
if ( '' !== $term->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', $force_refresh );
}
}
public function translate_title( $title, $post_id ) {
return $this->translated_value( $title, 'post:' . $post_id . ':post_title' );
}
@@ -114,6 +151,18 @@ class WPT_Content_Controller {
return $this->translated_value( $description, 'term:' . $term_id . ':description' );
}
public function translate_term_object( $term, $taxonomy, $context = '' ) {
if ( ! $term instanceof WP_Term || ! in_array( $term->taxonomy, array( 'category', 'post_tag' ), true ) ) {
return $term;
}
$translated_term = clone $term;
$translated_term->name = $this->translated_value( $term->name, 'term:' . $term->term_id . ':name' );
$translated_term->description = $this->translated_value( $term->description, 'term:' . $term->term_id . ':description' );
return $translated_term;
}
public function translate_seo_value( $value ) {
$post_id = get_queried_object_id();
+9 -1
View File
@@ -6,6 +6,7 @@ class WPT_Language_Router {
public function register() {
add_filter( 'query_vars', array( $this, 'query_vars' ) );
add_action( 'parse_request', array( $this, 'resolve_domain_language' ) );
add_filter( 'home_url', array( $this, 'localize_home_url' ), 20, 4 );
$this->register_rewrite_rules();
}
@@ -81,10 +82,17 @@ class WPT_Language_Router {
return $url;
}
$scheme = isset( $parts['scheme'] ) ? $parts['scheme'] . '://' : '//';
$host = isset( $parts['host'] ) ? $parts['host'] : '';
$port = isset( $parts['port'] ) ? ':' . $parts['port'] : '';
$path = '/' . $language . '/' . ltrim( $parts['path'], '/' );
$query = isset( $parts['query'] ) ? '?' . $parts['query'] : '';
$fragment = isset( $parts['fragment'] ) ? '#' . $parts['fragment'] : '';
return home_url( $path ) . $query . $fragment;
return $scheme . $host . $port . $path . $query . $fragment;
}
public function localize_home_url( $url, $path, $scheme, $blog_id ) {
return $this->localized_url( $url );
}
}