This commit is contained in:
hanyixuanten
2026-07-25 23:59:54 +08:00
parent ac7cfdf9f8
commit 9a124c7791
7 changed files with 60 additions and 15 deletions
+3 -1
View File
@@ -35,7 +35,9 @@ Only the listed SEO meta fields are sent for translation. Arbitrary metadata, se
## 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.
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.
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.
+11 -9
View File
@@ -22,8 +22,10 @@ class WPT_Admin {
foreach ( preg_split( '/\r\n|\r|\n/', (string) ( $settings['domain_bindings'] ?? '' ) ) as $line ) {
$parts = array_map( 'trim', explode( '=', $line, 2 ) );
if ( 2 === count( $parts ) && '' !== $parts[0] && '' !== $parts[1] ) {
$bindings[ sanitize_text_field( $parts[0] ) ] = sanitize_key( $parts[1] );
$domain = 2 === count( $parts ) ? WPT_Settings::normalize_domain( $parts[0] ) : '';
$language = 2 === count( $parts ) ? sanitize_key( $parts[1] ) : '';
if ( '' !== $domain && '' !== $language ) {
$bindings[ $domain ] = $language;
}
}
@@ -46,16 +48,16 @@ class WPT_Admin {
}
?>
<div class="wrap">
<h1><?php esc_html_e( 'WP Translate', 'wp-translate' ); ?></h1>
<h1>WP Translate</h1>
<form action="options.php" method="post">
<?php settings_fields( 'wpt_settings' ); ?>
<table class="form-table" role="presentation">
<tr><th scope="row"><label for="wpt-source-language"><?php esc_html_e( 'Source language', 'wp-translate' ); ?></label></th><td><input id="wpt-source-language" name="wpt_settings[source_language]" type="text" value="<?php echo esc_attr( $settings['source_language'] ); ?>" /></td></tr>
<tr><th scope="row"><label for="wpt-target-languages"><?php esc_html_e( 'Target languages', 'wp-translate' ); ?></label></th><td><input id="wpt-target-languages" name="wpt_settings[target_languages]" type="text" value="<?php echo esc_attr( implode( ',', $settings['target_languages'] ) ); ?>" class="regular-text" /></td></tr>
<tr><th scope="row"><label for="wpt-routing-mode"><?php esc_html_e( 'URL mode', 'wp-translate' ); ?></label></th><td><select id="wpt-routing-mode" name="wpt_settings[routing_mode]"><option value="subdirectory" <?php selected( $settings['routing_mode'], 'subdirectory' ); ?>><?php esc_html_e( 'Subdirectory', 'wp-translate' ); ?></option><option value="domain" <?php selected( $settings['routing_mode'], 'domain' ); ?>><?php esc_html_e( 'Domain binding', 'wp-translate' ); ?></option></select></td></tr>
<tr><th scope="row"><label for="wpt-domain-bindings"><?php esc_html_e( 'Domain bindings', 'wp-translate' ); ?></label></th><td><textarea id="wpt-domain-bindings" name="wpt_settings[domain_bindings]" rows="4" class="large-text"><?php echo esc_textarea( implode( "\n", $bindings ) ); ?></textarea></td></tr>
<tr><th scope="row"><label for="wpt-baidu-app-id"><?php esc_html_e( 'Baidu app ID', 'wp-translate' ); ?></label></th><td><input id="wpt-baidu-app-id" name="wpt_settings[baidu_app_id]" type="text" value="<?php echo esc_attr( $settings['baidu_app_id'] ); ?>" class="regular-text" /></td></tr>
<tr><th scope="row"><label for="wpt-baidu-secret-key"><?php esc_html_e( 'Baidu secret key', 'wp-translate' ); ?></label></th><td><input id="wpt-baidu-secret-key" name="wpt_settings[baidu_secret_key]" type="password" value="<?php echo esc_attr( $settings['baidu_secret_key'] ); ?>" class="regular-text" autocomplete="new-password" /></td></tr>
<tr><th scope="row"><label for="wpt-source-language">源语言</label></th><td><input id="wpt-source-language" name="wpt_settings[source_language]" type="text" value="<?php echo esc_attr( $settings['source_language'] ); ?>" /><p class="description">使用百度翻译语言代码,例如 <code>zh</code>。</p></td></tr>
<tr><th scope="row"><label for="wpt-target-languages">目标语言</label></th><td><input id="wpt-target-languages" name="wpt_settings[target_languages]" type="text" value="<?php echo esc_attr( implode( ',', $settings['target_languages'] ) ); ?>" class="regular-text" /><p class="description">多个语言代码用英文逗号分隔,例如 <code>en,ja</code>。</p></td></tr>
<tr><th scope="row"><label for="wpt-routing-mode">网址模式</label></th><td><select id="wpt-routing-mode" name="wpt_settings[routing_mode]"><option value="subdirectory" <?php selected( $settings['routing_mode'], 'subdirectory' ); ?>>子目录</option><option value="domain" <?php selected( $settings['routing_mode'], 'domain' ); ?>>绑定域名</option></select></td></tr>
<tr><th scope="row"><label for="wpt-domain-bindings">域名绑定</label></th><td><textarea id="wpt-domain-bindings" name="wpt_settings[domain_bindings]" rows="4" class="large-text" placeholder="en.example.com=en"><?php echo esc_textarea( implode( "\n", $bindings ) ); ?></textarea><p class="description">每行一个 <code>域名=语言代码</code>,例如 <code>en.example.com=en</code>。请勿填写 <code>https://</code>、路径或端口。</p></td></tr>
<tr><th scope="row"><label for="wpt-baidu-app-id">百度应用 ID</label></th><td><input id="wpt-baidu-app-id" name="wpt_settings[baidu_app_id]" type="text" value="<?php echo esc_attr( $settings['baidu_app_id'] ); ?>" class="regular-text" /></td></tr>
<tr><th scope="row"><label for="wpt-baidu-secret-key">百度密钥</label></th><td><input id="wpt-baidu-secret-key" name="wpt_settings[baidu_secret_key]" type="password" value="<?php echo esc_attr( $settings['baidu_secret_key'] ); ?>" class="regular-text" autocomplete="new-password" /></td></tr>
</table>
<?php submit_button(); ?>
</form>
+19 -4
View File
@@ -4,10 +4,25 @@ defined( 'ABSPATH' ) || exit;
class WPT_Language_Router {
public function register() {
add_rewrite_tag( '%wpt_language%', '([a-zA-Z-]+)' );
add_rewrite_rule( '^([a-zA-Z-]+)/(.*)?$', 'index.php?wpt_language=$matches[1]&pagename=$matches[2]', 'top' );
add_filter( 'query_vars', array( $this, 'query_vars' ) );
add_action( 'parse_request', array( $this, 'resolve_domain_language' ) );
$this->register_rewrite_rules();
}
public function register_rewrite_rules() {
if ( 'subdirectory' !== WPT_Settings::get()['routing_mode'] ) {
return;
}
$language_pattern = implode( '|', array_map( 'preg_quote', array_map( 'strtolower', WPT_Settings::target_languages() ) ) );
if ( '' === $language_pattern ) {
return;
}
add_rewrite_tag( '%wpt_language%', '(' . $language_pattern . ')' );
add_rewrite_rule( '^(' . $language_pattern . ')/(.*)$', 'index.php?wpt_language=$matches[1]&pagename=$matches[2]', 'top' );
add_rewrite_rule( '^(' . $language_pattern . ')/?$', 'index.php?wpt_language=$matches[1]', 'top' );
}
public function query_vars( $query_vars ) {
@@ -18,10 +33,10 @@ class WPT_Language_Router {
public function resolve_domain_language( $wp ) {
$settings = WPT_Settings::get();
$host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
$host = isset( $_SERVER['HTTP_HOST'] ) ? WPT_Settings::normalize_domain( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
$bindings = (array) $settings['domain_bindings'];
if ( 'domain' === $settings['routing_mode'] && isset( $bindings[ $host ] ) && WPT_Settings::is_supported_language( $bindings[ $host ] ) ) {
if ( 'domain' === $settings['routing_mode'] && '' !== $host && isset( $bindings[ $host ] ) && WPT_Settings::is_supported_language( $bindings[ $host ] ) ) {
$wp->query_vars['wpt_language'] = sanitize_key( $bindings[ $host ] );
}
}
+3
View File
@@ -40,6 +40,8 @@ class WPT_Plugin {
'',
false
);
$router = new WPT_Language_Router();
$router->register_rewrite_rules();
flush_rewrite_rules();
}
@@ -55,6 +57,7 @@ class WPT_Plugin {
public function refresh_rewrite_rules( $old_value, $new_value ) {
if ( (array) $old_value !== (array) $new_value ) {
$this->router->register_rewrite_rules();
flush_rewrite_rules();
}
}
+9
View File
@@ -27,4 +27,13 @@ class WPT_Settings {
return in_array( sanitize_key( $language ), $languages, true );
}
public static function normalize_domain( $domain ) {
$domain = strtolower( trim( (string) $domain ) );
$domain = preg_replace( '#^https?://#', '', $domain );
$domain = explode( '/', $domain, 2 )[0];
$domain = preg_replace( '/:\d+$/', '', $domain );
return preg_match( '/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/', $domain ) ? $domain : '';
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php
use PHPUnit\Framework\TestCase;
class SettingsTest extends TestCase {
public function test_normalize_domain_accepts_a_subdomain_and_removes_protocol_port_and_path() {
$this->assertSame( 'en.example.com', WPT_Settings::normalize_domain( 'https://EN.Example.com:8443/news' ) );
}
public function test_normalize_domain_rejects_a_domain_without_a_public_suffix() {
$this->assertSame( '', WPT_Settings::normalize_domain( 'invalid-domain' ) );
}
}
+2 -1
View File
@@ -6,4 +6,5 @@ require_once dirname( __DIR__ ) . '/includes/interface-wpt-translation-provider.
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-translation-service.php';
require_once dirname( __DIR__ ) . '/includes/class-wpt-settings.php';