fix html translate

This commit is contained in:
hanyixuanten
2026-07-26 00:49:26 +08:00
parent d55134b806
commit c12e3d699e
3 changed files with 52 additions and 108 deletions
+13 -12
View File
@@ -157,23 +157,24 @@ class WPT_Content_Controller {
return $this->translated_value( $content, 'post:' . $post_id . ':post_content' );
}
$protected = WPT_Content_Translator::protect( $content );
if ( false === $protected ) {
$segments = WPT_Content_Translator::segments( $content );
if ( false === $segments ) {
return $content;
}
$translated_template = $this->translated_value( $protected['template'], 'post:' . $post_id . ':post_content:template' );
if ( $translated_template === $protected['template'] ) {
return $content;
$text_index = 0;
foreach ( $segments as $index => $segment ) {
if ( WPT_Content_Translator::is_tag( $segment ) || '' === trim( $segment ) ) {
continue;
}
$whitespace = WPT_Content_Translator::surrounding_whitespace( $segment );
$translated = $this->translated_value( $whitespace['text'], 'post:' . $post_id . ':post_content:text:' . $text_index );
$segments[ $index ] = $whitespace['leading'] . $translated . $whitespace['trailing'];
++$text_index;
}
$anchors = array();
foreach ( $protected['anchors'] as $index => $anchor ) {
$translated_text = $this->translated_value( $anchor['text'], 'post:' . $post_id . ':post_content:anchor:' . $index );
$anchors[ $index ] = '<a' . $anchor['attributes'] . '>' . $translated_text . '</a>';
}
return WPT_Content_Translator::restore( $translated_template, $anchors, $protected['tags'] );
return implode( '', $segments );
}
public function translate_excerpt( $excerpt, $post ) {
+28 -74
View File
@@ -10,97 +10,51 @@ class WPT_Content_Translator {
}
public function translate( $content, $source_language, $target_language, $context, $force_refresh = false ) {
$protected = self::protect( $content );
if ( false === $protected ) {
$segments = self::segments( $content );
if ( false === $segments ) {
return WPT_Translation_Result::failure( 'content_parse_failed', 'Unable to protect links before translation.' );
}
$template_result = $this->service->get_or_translate( $protected['template'], $source_language, $target_language, $context . ':template', $force_refresh );
if ( ! $template_result->success ) {
return $template_result;
}
$anchors = array();
foreach ( $protected['anchors'] as $index => $anchor ) {
$link_result = $this->service->get_or_translate(
$anchor['text'],
$source_language,
$target_language,
$context . ':anchor:' . $index,
$force_refresh
);
if ( ! $link_result->success ) {
return $link_result;
$text_index = 0;
foreach ( $segments as $segment ) {
if ( self::is_tag( $segment ) || '' === trim( $segment ) ) {
continue;
}
$anchors[ $index ] = '<a' . $anchor['attributes'] . '>' . $link_result->value . '</a>';
$result = $this->service->get_or_translate( trim( $segment ), $source_language, $target_language, $context . ':text:' . $text_index, $force_refresh );
if ( ! $result->success ) {
return $result;
}
++$text_index;
}
return WPT_Translation_Result::success( self::restore( $template_result->value, $anchors, $protected['tags'] ) );
return WPT_Translation_Result::success( $content );
}
public static function protect( $content ) {
$anchors = array();
$template = preg_replace_callback(
'#<a\b([^>]*)>(.*?)</a>#is',
function ( $matches ) use ( &$anchors ) {
$index = count( $anchors );
$anchors[ $index ] = array(
'attributes' => $matches[1],
'text' => $matches[2],
);
public static function segments( $content ) {
$segments = preg_split( '/(<[^>]+>)/s', $content, -1, PREG_SPLIT_DELIM_CAPTURE );
return self::token( 'ANCHOR', $index );
},
$content
);
if ( null === $template ) {
if ( false === $segments ) {
return false;
}
$tags = array();
$template = preg_replace_callback(
'#<[^>]+>#s',
function ( $matches ) use ( &$tags ) {
$index = count( $tags );
$tags[ $index ] = $matches[0];
return array_values( array_filter( $segments, static function ( $segment ) {
return '' !== $segment;
} ) );
}
return self::token( 'TAG', $index );
},
$template
);
public static function is_tag( $segment ) {
return 1 === preg_match( '/^<[^>]+>$/s', $segment );
}
if ( null === $template ) {
return false;
}
public static function surrounding_whitespace( $segment ) {
preg_match( '/^(\s*)(.*?)(\s*)$/s', $segment, $matches );
return array(
'template' => $template,
'anchors' => $anchors,
'tags' => $tags,
'leading' => $matches[1],
'text' => $matches[2],
'trailing' => $matches[3],
);
}
public static function restore( $content, $anchors, $tags ) {
foreach ( $anchors as $index => $anchor ) {
$content = preg_replace( self::token_pattern( 'ANCHOR', $index ), $anchor, $content );
}
foreach ( $tags as $index => $tag ) {
$content = preg_replace( self::token_pattern( 'TAG', $index ), $tag, $content );
}
return $content;
}
private static function token( $type, $index ) {
return 'WPTTOKEN' . $type . $index . 'END';
}
private static function token_pattern( $type, $index ) {
return '#(?:\[\s*)?WPT\s*TOKEN\s*' . $type . '\s*' . $index . '\s*END(?:\s*\]|)#i';
}
}
+11 -22
View File
@@ -12,30 +12,19 @@ class ContentTranslatorTest extends TestCase {
$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:anchor:0', $store->save_calls[1]['field_context'] );
$this->assertSame( 'Read <a href="https://example.com/docs" target="_blank">our guide</a>.', $result->value );
$this->assertSame( 3, $provider->calls );
$this->assertSame( 'post:1:post_content:text:0', $store->save_calls[0]['field_context'] );
$this->assertSame( 'post:1:post_content:text:1', $store->save_calls[1]['field_context'] );
$this->assertSame( 'post:1:post_content:text:2', $store->save_calls[2]['field_context'] );
}
public function test_restores_tokens_when_the_provider_wraps_them_in_brackets() {
$store = new WPT_Test_Translation_Store();
$provider = new class implements WPT_Translation_Provider {
public function get_version() {
return 'test-provider-v1';
}
public function test_splits_text_while_preserving_links_images_and_other_tags() {
$segments = WPT_Content_Translator::segments( 'See <a href="/docs">docs</a><img src="/hero.jpg" alt="Hero"><strong>now</strong>.' );
public function translate( $source_value, $source_language, $target_language, $context ) {
$value = preg_replace( '/(WPTTOKEN[A-Z]+\d+END)/', '[$1]', $source_value );
return WPT_Translation_Result::success( $value );
}
};
$translator = new WPT_Content_Translator( new WPT_Translation_Service( $store, $provider ) );
$result = $translator->translate( 'See <a href="/docs">docs</a><img src="/hero.jpg" alt="Hero">.', 'en', 'zh', 'post:1:post_content' );
$this->assertTrue( $result->success );
$this->assertSame( 'See <a href="/docs">docs</a><img src="/hero.jpg" alt="Hero">.', $result->value );
$this->assertSame(
array( 'See ', '<a href="/docs">', 'docs', '</a>', '<img src="/hero.jpg" alt="Hero">', '<strong>', 'now', '</strong>', '.' ),
$segments
);
}
}