fix: links may encounter translate error

This commit is contained in:
hanyixuanten
2026-07-26 00:35:36 +08:00
parent e8b0fc9aeb
commit a4f43bce2f
6 changed files with 121 additions and 2 deletions
+2
View File
@@ -33,6 +33,8 @@ 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.
When post content contains HTML links, the plugin preserves each link's `href` and attributes, translates its visible text as a separate value, and inserts that translated text back into the original link. Re-run a post translation after upgrading to regenerate previously translated content that contains links.
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
+37 -2
View File
@@ -81,7 +81,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, $force_refresh );
$context = 'post:' . $post_id . ':' . $field;
if ( 'post_content' === $field ) {
( new WPT_Content_Translator( $service ) )->translate( $value, $settings['source_language'], $target_language, $context, $force_refresh );
} else {
$service->get_or_translate( $value, $settings['source_language'], $target_language, $context, $force_refresh );
}
}
}
@@ -142,7 +147,37 @@ class WPT_Content_Controller {
}
public function translate_content( $content ) {
return $this->translated_value( $content, 'post:' . get_the_ID() . ':post_content' );
$post_id = get_the_ID();
if ( ! $post_id || false === stripos( $content, '<a' ) ) {
return $this->translated_value( $content, 'post:' . $post_id . ':post_content' );
}
$links = array();
$template = preg_replace_callback(
'#<a\b([^>]*)>(.*?)</a>#is',
function ( $matches ) use ( &$links ) {
$index = count( $links );
$links[] = $matches;
return '[[WPT_LINK_' . $index . ']]';
},
$content
);
if ( null === $template ) {
return $content;
}
$translated_template = $this->translated_value( $template, 'post:' . $post_id . ':post_content:template' );
if ( $translated_template === $template ) {
return $content;
}
foreach ( $links as $index => $link ) {
$translated_text = $this->translated_value( $link[2], 'post:' . $post_id . ':post_content:link:' . $index );
$translated_template = str_replace( '[[WPT_LINK_' . $index . ']]', '<a' . $link[1] . '>' . $translated_text . '</a>', $translated_template );
}
return $translated_template;
}
public function translate_excerpt( $excerpt, $post ) {
+60
View File
@@ -0,0 +1,60 @@
<?php
defined( 'ABSPATH' ) || exit;
class WPT_Content_Translator {
private $service;
public function __construct( WPT_Translation_Service $service ) {
$this->service = $service;
}
public function translate( $content, $source_language, $target_language, $context, $force_refresh = false ) {
$links = array();
$template = preg_replace_callback(
'#<a\b([^>]*)>(.*?)</a>#is',
function ( $matches ) use ( &$links ) {
$index = count( $links );
$links[ $index ] = array(
'attributes' => $matches[1],
'text' => $matches[2],
);
return '[[WPT_LINK_' . $index . ']]';
},
$content
);
if ( null === $template ) {
return WPT_Translation_Result::failure( 'content_parse_failed', 'Unable to protect links before translation.' );
}
$template_context = empty( $links ) ? $context : $context . ':template';
$template_result = $this->service->get_or_translate( $template, $source_language, $target_language, $template_context, $force_refresh );
if ( ! $template_result->success || empty( $links ) ) {
return $template_result;
}
$translated_content = $template_result->value;
foreach ( $links as $index => $link ) {
$link_result = $this->service->get_or_translate(
$link['text'],
$source_language,
$target_language,
$context . ':link:' . $index,
$force_refresh
);
if ( ! $link_result->success ) {
return $link_result;
}
$anchor = '<a' . $link['attributes'] . '>' . $link_result->value . '</a>';
$translated_content = str_replace( '[[WPT_LINK_' . $index . ']]', $anchor, $translated_content );
}
return WPT_Translation_Result::success( $translated_content );
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
use PHPUnit\Framework\TestCase;
class ContentTranslatorTest extends TestCase {
public function test_translates_link_text_separately_and_preserves_link_attributes() {
$store = new WPT_Test_Translation_Store();
$provider = new WPT_Test_Translation_Provider();
$service = new WPT_Translation_Service( $store, $provider );
$translator = new WPT_Content_Translator( $service );
$result = $translator->translate( 'Read <a href="https://example.com/docs" target="_blank">our guide</a>.', 'en', 'zh', 'post:1:post_content' );
$this->assertTrue( $result->success );
$this->assertSame( '[zh] Read <a href="https://example.com/docs" target="_blank">[zh] our guide</a>.', $result->value );
$this->assertSame( 2, $provider->calls );
$this->assertSame( 'post:1:post_content:template', $store->save_calls[0]['field_context'] );
$this->assertSame( 'post:1:post_content:link:0', $store->save_calls[1]['field_context'] );
}
}
+1
View File
@@ -7,4 +7,5 @@ require_once dirname( __DIR__ ) . '/includes/class-wpt-translation-result.php';
require_once dirname( __DIR__ ) . '/includes/class-wpt-translation-identity.php';
require_once dirname( __DIR__ ) . '/includes/class-wpt-translation-store.php';
require_once dirname( __DIR__ ) . '/includes/class-wpt-translation-service.php';
require_once dirname( __DIR__ ) . '/includes/class-wpt-content-translator.php';
require_once dirname( __DIR__ ) . '/includes/class-wpt-settings.php';
+1
View File
@@ -21,6 +21,7 @@ require_once WPT_PATH . 'includes/class-wpt-translation-identity.php';
require_once WPT_PATH . 'includes/class-wpt-translation-store.php';
require_once WPT_PATH . 'includes/class-wpt-baidu-provider.php';
require_once WPT_PATH . 'includes/class-wpt-translation-service.php';
require_once WPT_PATH . 'includes/class-wpt-content-translator.php';
require_once WPT_PATH . 'includes/class-wpt-settings.php';
require_once WPT_PATH . 'includes/class-wpt-language-router.php';
require_once WPT_PATH . 'includes/class-wpt-content-controller.php';