v1.1.0
This commit is contained in:
parent
a157afe625
commit
02cd01e862
20 changed files with 1365 additions and 646 deletions
|
|
@ -30,7 +30,7 @@
|
|||
/**
|
||||
* Calls the REST endpoint to translate the selected fields of the saved post.
|
||||
*
|
||||
* @param {Array<string>} fields Field names to translate ('title', 'content').
|
||||
* @param {Array<string>} fields Field names to translate ('title', 'content', 'excerpt').
|
||||
* @param {string} targetLocale WordPress locale code.
|
||||
* @returns {Promise<Object>} Promise resolving to the translated values.
|
||||
*/
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
} );
|
||||
}
|
||||
|
||||
function resolveFields( wantTitle, wantContent ) {
|
||||
function resolveFields( wantTitle, wantContent, wantExcerpt ) {
|
||||
var fields = [];
|
||||
if ( wantTitle && data.settings.translateTitle ) {
|
||||
fields.push( 'title' );
|
||||
|
|
@ -58,6 +58,9 @@
|
|||
if ( wantContent && data.settings.translateContent ) {
|
||||
fields.push( 'content' );
|
||||
}
|
||||
if ( wantExcerpt && data.settings.translateExcerpt ) {
|
||||
fields.push( 'excerpt' );
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +75,7 @@
|
|||
* Applies a translation response using the right method for the current editor.
|
||||
*
|
||||
* In the block editor, uses wp.data dispatch (and wp.blocks.parse for content).
|
||||
* In the classic editor, updates the DOM directly (title input + TinyMCE).
|
||||
* In the classic editor, updates the DOM directly (title input + TinyMCE + excerpt textarea).
|
||||
*
|
||||
* @param {Object} response Translation response.
|
||||
* @param {Document} doc Document where the metabox lives (for classic fallback).
|
||||
|
|
@ -98,6 +101,9 @@
|
|||
wp.data.dispatch( 'core/editor' ).editPost( { content: response.content } );
|
||||
}
|
||||
}
|
||||
if ( response.excerpt !== undefined ) {
|
||||
wp.data.dispatch( 'core/editor' ).editPost( { excerpt: response.excerpt } );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +117,12 @@
|
|||
if ( response.content !== undefined ) {
|
||||
applyContentToClassic( response.content, doc );
|
||||
}
|
||||
if ( response.excerpt !== undefined ) {
|
||||
var excerptTextarea = doc.getElementById( 'excerpt' );
|
||||
if ( excerptTextarea ) {
|
||||
excerptTextarea.value = response.excerpt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyContentToClassic( content, doc ) {
|
||||
|
|
@ -159,7 +171,11 @@
|
|||
return;
|
||||
}
|
||||
|
||||
var fields = resolveFields( data.settings.translateTitle, data.settings.translateContent );
|
||||
var fields = resolveFields(
|
||||
data.settings.translateTitle,
|
||||
data.settings.translateContent,
|
||||
data.settings.translateExcerpt
|
||||
);
|
||||
if ( fields.length === 0 ) {
|
||||
setStatus( labels.noFields || '', 'warning' );
|
||||
return;
|
||||
|
|
@ -244,6 +260,10 @@
|
|||
var translateContent = contentState[0];
|
||||
var setTranslateContent = contentState[1];
|
||||
|
||||
var excerptState = useState( !! data.settings.translateExcerpt );
|
||||
var translateExcerpt = excerptState[0];
|
||||
var setTranslateExcerpt = excerptState[1];
|
||||
|
||||
var coreEditor = dataStore.select( 'core/editor' );
|
||||
var isDirty = coreEditor && typeof coreEditor.isEditedPostDirty === 'function' ? coreEditor.isEditedPostDirty() : false;
|
||||
|
||||
|
|
@ -253,7 +273,7 @@
|
|||
);
|
||||
}
|
||||
|
||||
if ( ! data.settings.translateTitle && ! data.settings.translateContent ) {
|
||||
if ( ! data.settings.translateTitle && ! data.settings.translateContent && ! data.settings.translateExcerpt ) {
|
||||
return el( 'div', { className: 'ai-translator-sidebar' },
|
||||
el( components.Notice, { status: 'info', isDismissible: false }, labels.noFields || '' )
|
||||
);
|
||||
|
|
@ -270,7 +290,7 @@
|
|||
} );
|
||||
|
||||
function runTranslate() {
|
||||
var fields = resolveFields( translateTitle, translateContent );
|
||||
var fields = resolveFields( translateTitle, translateContent, translateExcerpt );
|
||||
|
||||
if ( fields.length === 0 ) {
|
||||
setNotice( { status: 'warning', message: labels.noFields || '' } );
|
||||
|
|
@ -350,6 +370,17 @@
|
|||
} ) );
|
||||
}
|
||||
|
||||
if ( data.settings.translateExcerpt ) {
|
||||
children.push( el( components.CheckboxControl, {
|
||||
key: 'excerpt',
|
||||
label: labels.translateExcerpt || '',
|
||||
checked: translateExcerpt,
|
||||
onChange: function ( value ) {
|
||||
setTranslateExcerpt( !! value );
|
||||
},
|
||||
} ) );
|
||||
}
|
||||
|
||||
children.push( el( components.Button, {
|
||||
key: 'submit',
|
||||
variant: 'primary',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,39 @@
|
|||
== Changelog ==
|
||||
|
||||
= 1.1.0 =
|
||||
|
||||
_Release date: 2026-05-25_
|
||||
|
||||
**Highlights**
|
||||
|
||||
* MultilingualPress integration: new connected posts are automatically translated at creation time.
|
||||
* Excerpt translation: `post_excerpt` is now a translatable field.
|
||||
* Removed dependency on the third-party AI plugin — uses the native WordPress 7.0 AI client.
|
||||
|
||||
**Added**
|
||||
|
||||
* Excerpt field translation (title, content, and excerpt are now independently toggleable).
|
||||
* MultilingualPress integration: opt-in "Auto-translate new connected posts" toggle in the settings pages. When enabled and MLP creates a new connected post, the plugin translates the enabled fields synchronously into the target site's language.
|
||||
|
||||
**Changed**
|
||||
|
||||
* `Requires Plugins: ai` removed from plugin headers. `wp_ai_client_prompt()` is native to WordPress 7.0 — the plugin works with any provider configured in Settings → AI.
|
||||
* "AI not active" admin notice replaced by a "no provider configured" notice, which fires when no provider supports text generation.
|
||||
|
||||
**Compatibility**
|
||||
|
||||
* WordPress: 7.0 - 7.1
|
||||
* PHP: 7.4 - 8.5
|
||||
* WP-CLI: 2.12 or newer
|
||||
* MultilingualPress: 5.x (optional)
|
||||
|
||||
**Tests**
|
||||
|
||||
* PHP Coding Standards: WordPress-Core, WordPress-Docs, WordPress-Extra
|
||||
* PHPCompatibility: 7.4 - 8.5
|
||||
* PHPStan: level 9
|
||||
* PHPUnit: 50/50 tests passing (single-site)
|
||||
|
||||
= 1.0.0 =
|
||||
|
||||
_Release date: 2026-05-23_
|
||||
|
|
@ -21,12 +55,13 @@ _Release date: 2026-05-23_
|
|||
|
||||
**Compatibility**
|
||||
|
||||
* WordPress: 7.0
|
||||
* WordPress: 7.0 - 7.1
|
||||
* PHP: 7.4 - 8.5
|
||||
* WP-CLI: 2.10 or newer
|
||||
* WP-CLI: 2.12 or newer
|
||||
|
||||
**Tests**
|
||||
|
||||
* PHP Coding Standards: WordPress-Core, WordPress-Docs, WordPress-Extra
|
||||
* PHPCompatibility: 7.4 - 8.5
|
||||
* PHPStan: level 9
|
||||
* PHPUnit: 50/50 tests passing (single-site)
|
||||
|
|
|
|||
476
class-robotstxt-updater.php
Normal file
476
class-robotstxt-updater.php
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
<?php
|
||||
/**
|
||||
* Generic JSON-based updater for ROBOTSTXT plugins.
|
||||
*
|
||||
* Copy this file into any ROBOTSTXT plugin. It auto-configures itself by
|
||||
* reading the plugin headers and looking for a `Gitea Plugin URI` header to
|
||||
* construct the remote update.json URL.
|
||||
*
|
||||
* Usage in the main plugin file:
|
||||
* require_once AI_TRANSLATOR_DIR . 'class-robotstxt-updater.php';
|
||||
* Robotstxt_Updater::init( AI_TRANSLATOR_FILE );
|
||||
*
|
||||
* @package ROBOTSTXT
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'Robotstxt_Updater' ) ) {
|
||||
|
||||
/**
|
||||
* Generic updater that works with any ROBOTSTXT plugin.
|
||||
*
|
||||
* Reads the plugin headers to construct the remote update.json URL and
|
||||
* hooks into WordPress's standard update mechanism.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Robotstxt_Updater {
|
||||
|
||||
/**
|
||||
* Absolute path to the main plugin file.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
private $plugin_file_path;
|
||||
|
||||
/**
|
||||
* Plugin basename (e.g. 'my-plugin/my-plugin.php').
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
private $plugin_basename;
|
||||
|
||||
/**
|
||||
* Plugin slug (directory name).
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
private $plugin_slug;
|
||||
|
||||
/**
|
||||
* URL of the remote update.json file.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
private $json_url;
|
||||
|
||||
/**
|
||||
* Transient cache key for this plugin's remote data.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
private $cache_key;
|
||||
|
||||
/**
|
||||
* Parsed plugin file headers.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var array<string,bool|string>
|
||||
*/
|
||||
private $plugin_data;
|
||||
|
||||
/**
|
||||
* Creates the updater instance and registers WordPress hooks.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $plugin_file_path Absolute path to the main plugin file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init( $plugin_file_path ) {
|
||||
$instance = new self( (string) $plugin_file_path );
|
||||
$instance->register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor — populates instance state from the plugin file headers.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $plugin_file_path Absolute path to the main plugin file.
|
||||
*/
|
||||
private function __construct( $plugin_file_path ) {
|
||||
$this->plugin_file_path = (string) $plugin_file_path;
|
||||
$this->plugin_basename = plugin_basename( $this->plugin_file_path );
|
||||
$this->plugin_slug = dirname( $this->plugin_basename );
|
||||
$this->plugin_data = $this->get_plugin_data();
|
||||
$this->json_url = $this->build_json_url();
|
||||
$this->cache_key = 'robotstxt_updater_' . md5( $this->plugin_basename );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the WordPress hooks needed for update checking.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function register() {
|
||||
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'inject_update_info' ) );
|
||||
add_filter( 'plugins_api', array( $this, 'provide_plugin_details' ), 10, 3 );
|
||||
add_action( 'admin_init', array( $this, 'handle_cache_clear' ) );
|
||||
add_action( 'robotstxt_updater_clear_cache', array( $this, 'clear_cache' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin headers, loading the admin helper file if needed.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array<string,bool|string>
|
||||
*/
|
||||
private function get_plugin_data() {
|
||||
if ( ! function_exists( 'get_plugin_data' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$data = get_plugin_data( $this->plugin_file_path, false, false );
|
||||
|
||||
return is_array( $data ) ? $data : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the remote update.json URL from the plugin headers.
|
||||
*
|
||||
* Uses the `Gitea Plugin URI` header when present. Falls back to the
|
||||
* `Plugin URI` header for legacy configurations. If neither yields a
|
||||
* usable URL, constructs one from the plugin slug.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return string Fully-qualified URL.
|
||||
*/
|
||||
private function build_json_url() {
|
||||
$gitea_uri = isset( $this->plugin_data['Gitea Plugin URI'] )
|
||||
? (string) $this->plugin_data['Gitea Plugin URI']
|
||||
: '';
|
||||
|
||||
if ( '' !== $gitea_uri ) {
|
||||
// Full URL already provided.
|
||||
if ( 0 === strpos( $gitea_uri, 'http' ) ) {
|
||||
return rtrim( $gitea_uri, '/' ) . '/raw/branch/main/update.json';
|
||||
}
|
||||
|
||||
// "OWNER/REPO" short-form.
|
||||
if ( (bool) preg_match( '#^[^/]+/[^/]+$#', $gitea_uri ) ) {
|
||||
return 'https://git.robotstxt.es/' . $gitea_uri . '/raw/branch/main/update.json';
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Plugin URI on the Gitea server.
|
||||
$plugin_uri = isset( $this->plugin_data['PluginURI'] )
|
||||
? (string) $this->plugin_data['PluginURI']
|
||||
: '';
|
||||
|
||||
if ( '' !== $plugin_uri && false !== strpos( $plugin_uri, 'git.robotstxt.es' ) ) {
|
||||
return rtrim( $plugin_uri, '/' ) . '/raw/branch/main/update.json';
|
||||
}
|
||||
|
||||
// Last resort: derive from the plugin slug.
|
||||
return 'https://git.robotstxt.es/ROBOTSTXT/' . $this->plugin_slug . '/raw/branch/main/update.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects update information into WordPress's plugin update transient.
|
||||
*
|
||||
* Hooked to `pre_set_site_transient_update_plugins`.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param mixed $transient The update_plugins transient value.
|
||||
*
|
||||
* @return mixed The (possibly modified) transient.
|
||||
*/
|
||||
public function inject_update_info( $transient ) {
|
||||
if ( ! is_object( $transient ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type narrowed to stdClass after is_object() check above.
|
||||
*
|
||||
* @var stdClass $transient
|
||||
*/
|
||||
|
||||
if ( empty( $transient->checked ) || ! is_array( $transient->checked ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( empty( $transient->checked[ $this->plugin_basename ] ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
$checked_value = $transient->checked[ $this->plugin_basename ];
|
||||
$current_version = is_scalar( $checked_value ) ? (string) $checked_value : '';
|
||||
$remote = $this->get_remote_data();
|
||||
|
||||
if ( ! isset( $remote['version'], $remote['download_url'] ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( ! is_string( $remote['version'] ) || ! is_string( $remote['download_url'] ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( '' === $remote['download_url'] ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( ! $this->is_compatible( $remote ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( version_compare( $remote['version'], $current_version, '>' ) ) {
|
||||
$slug = isset( $remote['slug'] ) && is_string( $remote['slug'] ) ? $remote['slug'] : $this->plugin_slug;
|
||||
$homepage = isset( $remote['homepage'] ) && is_string( $remote['homepage'] ) ? $remote['homepage'] : '';
|
||||
|
||||
if ( '' === $homepage && isset( $this->plugin_data['PluginURI'] ) ) {
|
||||
$homepage = (string) $this->plugin_data['PluginURI'];
|
||||
}
|
||||
|
||||
$update = (object) array(
|
||||
'slug' => $slug,
|
||||
'plugin' => $this->plugin_basename,
|
||||
'new_version' => $remote['version'],
|
||||
'url' => $homepage,
|
||||
'package' => $remote['download_url'],
|
||||
'tested' => isset( $remote['tested'] ) && is_string( $remote['tested'] ) ? $remote['tested'] : '',
|
||||
'requires' => isset( $remote['requires'] ) && is_string( $remote['requires'] ) ? $remote['requires'] : '',
|
||||
'requires_php' => isset( $remote['requires_php'] ) && is_string( $remote['requires_php'] ) ? $remote['requires_php'] : '',
|
||||
);
|
||||
|
||||
if ( ! isset( $transient->response ) || ! is_array( $transient->response ) ) {
|
||||
$transient->response = array();
|
||||
}
|
||||
|
||||
$transient->response[ $this->plugin_basename ] = $update;
|
||||
}
|
||||
|
||||
return $transient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides plugin details for the "View details" modal.
|
||||
*
|
||||
* Hooked to `plugins_api`.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param false|object|array<string,mixed> $result The existing result.
|
||||
* @param string $action The type of information being requested.
|
||||
* @param object $args Plugin API arguments.
|
||||
*
|
||||
* @return false|object|array<string,mixed>
|
||||
*/
|
||||
public function provide_plugin_details( $result, $action, $args ) {
|
||||
if ( 'plugin_information' !== $action ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( ! is_object( $args ) || empty( $args->slug ) || $args->slug !== $this->plugin_slug ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$remote = $this->get_remote_data();
|
||||
|
||||
if ( empty( $remote['version'] ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$name = isset( $remote['name'] ) && is_string( $remote['name'] ) ? $remote['name'] : ( isset( $this->plugin_data['Name'] ) ? (string) $this->plugin_data['Name'] : $this->plugin_slug );
|
||||
$slug = isset( $remote['slug'] ) && is_string( $remote['slug'] ) ? $remote['slug'] : $this->plugin_slug;
|
||||
$version = is_string( $remote['version'] ) ? $remote['version'] : '';
|
||||
$author = isset( $remote['author'] ) && is_string( $remote['author'] ) ? $remote['author'] : ( isset( $this->plugin_data['Author'] ) ? (string) $this->plugin_data['Author'] : '' );
|
||||
$homepage = isset( $remote['homepage'] ) && is_string( $remote['homepage'] ) ? $remote['homepage'] : ( isset( $this->plugin_data['PluginURI'] ) ? (string) $this->plugin_data['PluginURI'] : '' );
|
||||
$requires = isset( $remote['requires'] ) && is_string( $remote['requires'] ) ? $remote['requires'] : '';
|
||||
$tested = isset( $remote['tested'] ) && is_string( $remote['tested'] ) ? $remote['tested'] : '';
|
||||
$req_php = isset( $remote['requires_php'] ) && is_string( $remote['requires_php'] ) ? $remote['requires_php'] : '';
|
||||
$description = isset( $remote['description'] ) && is_string( $remote['description'] ) ? $remote['description'] : ( isset( $this->plugin_data['Description'] ) ? (string) $this->plugin_data['Description'] : '' );
|
||||
$changelog = isset( $remote['changelog'] ) && is_string( $remote['changelog'] ) ? $remote['changelog'] : '';
|
||||
$download = isset( $remote['download_url'] ) && is_string( $remote['download_url'] ) ? $remote['download_url'] : '';
|
||||
|
||||
return (object) array(
|
||||
'name' => $name,
|
||||
'slug' => $slug,
|
||||
'version' => $version,
|
||||
'author' => $author,
|
||||
'homepage' => $homepage,
|
||||
'requires' => $requires,
|
||||
'tested' => $tested,
|
||||
'requires_php' => $req_php,
|
||||
'sections' => array(
|
||||
'description' => $description,
|
||||
'changelog' => $changelog,
|
||||
),
|
||||
'download_link' => $download,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remote update data, reading from cache or fetching fresh.
|
||||
*
|
||||
* The data is stored with an HMAC signature (using AUTH_SALT) to detect
|
||||
* cache tampering. Falls back to unsigned caching when AUTH_SALT is empty.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function get_remote_data() {
|
||||
$cached = get_site_transient( $this->cache_key );
|
||||
|
||||
// Verify HMAC signature when AUTH_SALT is available.
|
||||
if ( false !== $cached && defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) {
|
||||
if ( is_array( $cached ) && isset( $cached['signature'], $cached['data'] ) && is_string( $cached['signature'] ) ) {
|
||||
$payload_data = is_array( $cached['data'] ) ? $cached['data'] : array();
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Used only for HMAC; data is never unserialized.
|
||||
$expected_sig = hash_hmac( 'sha256', $this->cache_key . serialize( $payload_data ), AUTH_SALT );
|
||||
|
||||
if ( hash_equals( $expected_sig, $cached['signature'] ) ) {
|
||||
return $payload_data;
|
||||
}
|
||||
|
||||
// Signature invalid — delete corrupted cache entry.
|
||||
delete_site_transient( $this->cache_key );
|
||||
$cached = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( false !== $cached ) {
|
||||
// Legacy cache format (no HMAC).
|
||||
return is_array( $cached ) ? $cached : array();
|
||||
}
|
||||
|
||||
$remote = $this->fetch_json();
|
||||
$data = is_array( $remote ) ? $remote : array();
|
||||
|
||||
if ( defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) {
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Used only for HMAC; data is never unserialized.
|
||||
$signature = hash_hmac( 'sha256', $this->cache_key . serialize( $data ), AUTH_SALT );
|
||||
$payload = array(
|
||||
'data' => $data,
|
||||
'timestamp' => time(),
|
||||
'signature' => $signature,
|
||||
);
|
||||
set_site_transient( $this->cache_key, $payload, 6 * HOUR_IN_SECONDS );
|
||||
} else {
|
||||
set_site_transient( $this->cache_key, $data, 6 * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and decodes the remote update.json file.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function fetch_json() {
|
||||
$response = wp_remote_get(
|
||||
$this->json_url,
|
||||
array(
|
||||
'timeout' => 10,
|
||||
'headers' => array(
|
||||
'Accept' => 'application/json',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$code = (int) wp_remote_retrieve_response_code( $response );
|
||||
|
||||
if ( $code < 200 || $code >= 300 ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$data = json_decode( $body, true );
|
||||
|
||||
return is_array( $data ) ? $data : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the remote version is compatible with the current environment.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array<string,mixed> $remote Remote update data.
|
||||
*
|
||||
* @return bool True if compatible.
|
||||
*/
|
||||
private function is_compatible( array $remote ) {
|
||||
if ( isset( $remote['requires_php'] ) && is_string( $remote['requires_php'] ) && '' !== $remote['requires_php'] ) {
|
||||
if ( version_compare( PHP_VERSION, $remote['requires_php'], '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $remote['requires'] ) && is_string( $remote['requires'] ) && '' !== $remote['requires'] ) {
|
||||
if ( version_compare( (string) get_bloginfo( 'version' ), $remote['requires'], '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a manual cache-clear request triggered via a URL parameter.
|
||||
*
|
||||
* Validates nonce and capability before clearing.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_cache_clear() {
|
||||
$clear_cache = filter_input( INPUT_GET, 'robotstxt_clear_update_cache', FILTER_UNSAFE_RAW );
|
||||
|
||||
if ( null === $clear_cache ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nonce_raw = filter_input( INPUT_GET, '_wpnonce', FILTER_UNSAFE_RAW );
|
||||
$nonce = is_string( $nonce_raw ) ? sanitize_text_field( wp_unslash( $nonce_raw ) ) : '';
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, 'robotstxt_clear_update_cache' ) ) {
|
||||
wp_die( esc_html__( 'Security check failed.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'update_plugins' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
$this->clear_cache();
|
||||
wp_safe_redirect( remove_query_arg( array( 'robotstxt_clear_update_cache', '_wpnonce' ) ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the cached remote data for this plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_cache() {
|
||||
delete_site_transient( $this->cache_key );
|
||||
delete_site_transient( 'update_plugins' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -221,16 +221,22 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
|||
<label for="ai-translator-translate-content">
|
||||
<input type="checkbox" id="ai-translator-translate-content" name="ai_translator_settings[translate_content]" value="1" <?php checked( $current['translate_content'] ); ?> />
|
||||
<?php echo esc_html__( 'Translate content', 'robotstxt-ai-translator' ); ?>
|
||||
</label><br />
|
||||
|
||||
<label for="ai-translator-translate-excerpt">
|
||||
<input type="checkbox" id="ai-translator-translate-excerpt" name="ai_translator_settings[translate_excerpt]" value="1" <?php checked( $current['translate_excerpt'] ); ?> />
|
||||
<?php echo esc_html__( 'Translate excerpt', 'robotstxt-ai-translator' ); ?>
|
||||
</label>
|
||||
|
||||
<?php if ( is_multisite() ) : ?>
|
||||
<p class="description">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: title default, 2: content default. */
|
||||
esc_html__( 'Network defaults: title %1$s, content %2$s.', 'robotstxt-ai-translator' ),
|
||||
/* translators: 1: title default, 2: content default, 3: excerpt default. */
|
||||
esc_html__( 'Network defaults: title %1$s, content %2$s, excerpt %3$s.', 'robotstxt-ai-translator' ),
|
||||
isset( $defaults['translate_title'] ) && $defaults['translate_title'] ? esc_html__( 'enabled', 'robotstxt-ai-translator' ) : esc_html__( 'disabled', 'robotstxt-ai-translator' ),
|
||||
isset( $defaults['translate_content'] ) && $defaults['translate_content'] ? esc_html__( 'enabled', 'robotstxt-ai-translator' ) : esc_html__( 'disabled', 'robotstxt-ai-translator' )
|
||||
isset( $defaults['translate_content'] ) && $defaults['translate_content'] ? esc_html__( 'enabled', 'robotstxt-ai-translator' ) : esc_html__( 'disabled', 'robotstxt-ai-translator' ),
|
||||
isset( $defaults['translate_excerpt'] ) && $defaults['translate_excerpt'] ? esc_html__( 'enabled', 'robotstxt-ai-translator' ) : esc_html__( 'disabled', 'robotstxt-ai-translator' )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
|
@ -238,6 +244,26 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
|||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<?php if ( class_exists( '\Inpsyde\MultilingualPress\TranslationUi\Post\MetaboxAction' ) ) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo esc_html__( 'MultilingualPress', 'robotstxt-ai-translator' ); ?></th>
|
||||
<td>
|
||||
<fieldset>
|
||||
<legend class="screen-reader-text"><?php echo esc_html__( 'MultilingualPress', 'robotstxt-ai-translator' ); ?></legend>
|
||||
|
||||
<label for="ai-translator-auto-translate-mlp">
|
||||
<input type="checkbox" id="ai-translator-auto-translate-mlp" name="ai_translator_settings[auto_translate_on_mlp_create]" value="1" <?php checked( $current['auto_translate_on_mlp_create'] ); ?> />
|
||||
<?php echo esc_html__( 'Auto-translate new connected posts', 'robotstxt-ai-translator' ); ?>
|
||||
</label>
|
||||
|
||||
<p class="description">
|
||||
<?php echo esc_html__( 'When MultilingualPress creates a new connected post, automatically translate its enabled fields into the target site\'s language. The translation runs synchronously during save.', 'robotstxt-ai-translator' ); ?>
|
||||
</p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
@ -315,6 +341,11 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
|||
<label for="ai-translator-net-translate-content">
|
||||
<input type="checkbox" id="ai-translator-net-translate-content" name="ai_translator_settings[translate_content]" value="1" <?php checked( $network['translate_content'] ); ?> />
|
||||
<?php echo esc_html__( 'Translate content', 'robotstxt-ai-translator' ); ?>
|
||||
</label><br />
|
||||
|
||||
<label for="ai-translator-net-translate-excerpt">
|
||||
<input type="checkbox" id="ai-translator-net-translate-excerpt" name="ai_translator_settings[translate_excerpt]" value="1" <?php checked( $network['translate_excerpt'] ); ?> />
|
||||
<?php echo esc_html__( 'Translate excerpt', 'robotstxt-ai-translator' ); ?>
|
||||
</label>
|
||||
|
||||
<p class="description">
|
||||
|
|
@ -323,6 +354,26 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
|||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<?php if ( class_exists( '\Inpsyde\MultilingualPress\TranslationUi\Post\MetaboxAction' ) ) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo esc_html__( 'MultilingualPress', 'robotstxt-ai-translator' ); ?></th>
|
||||
<td>
|
||||
<fieldset>
|
||||
<legend class="screen-reader-text"><?php echo esc_html__( 'MultilingualPress', 'robotstxt-ai-translator' ); ?></legend>
|
||||
|
||||
<label for="ai-translator-net-auto-translate-mlp">
|
||||
<input type="checkbox" id="ai-translator-net-auto-translate-mlp" name="ai_translator_settings[auto_translate_on_mlp_create]" value="1" <?php checked( $network['auto_translate_on_mlp_create'] ); ?> />
|
||||
<?php echo esc_html__( 'Auto-translate new connected posts', 'robotstxt-ai-translator' ); ?>
|
||||
</label>
|
||||
|
||||
<p class="description">
|
||||
<?php echo esc_html__( 'When MultilingualPress creates a new connected post, automatically translate its enabled fields into the target site\'s language. The translation runs synchronously during save.', 'robotstxt-ai-translator' ); ?>
|
||||
</p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
@ -514,6 +565,11 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
|||
/**
|
||||
* Sanitises a raw settings array into a strict boolean schema.
|
||||
*
|
||||
* This is the single sanitisation boundary for all form input from both the
|
||||
* site and network settings pages. Raw POST values arrive via wp_unslash()
|
||||
* and are converted here to strict booleans. Any new field added to this
|
||||
* schema MUST be sanitised in this method before being stored.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array<string,mixed> $raw Raw input from the form.
|
||||
|
|
@ -524,6 +580,8 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
|||
return array(
|
||||
'translate_title' => ! empty( $raw['translate_title'] ),
|
||||
'translate_content' => ! empty( $raw['translate_content'] ),
|
||||
'translate_excerpt' => ! empty( $raw['translate_excerpt'] ),
|
||||
'auto_translate_on_mlp_create' => ! empty( $raw['auto_translate_on_mlp_create'] ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,19 +124,8 @@ if ( ! class_exists( 'AI_Translator_CLI' ) ) {
|
|||
WP_CLI::error( __( 'The --post-id and --post-type flags are mutually exclusive.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
$current_user = wp_get_current_user();
|
||||
if ( ! $current_user || 0 === (int) $current_user->ID ) {
|
||||
WP_CLI::warning( __( 'No user context. Pass --user=<id|login> so post updates carry an author and capability checks apply.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
if ( ! $dry_run && ! $this->translator->is_available() ) {
|
||||
WP_CLI::error( __( 'The WordPress AI plugin is not active.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
if ( ! $dry_run && ! $this->translator->is_supported() ) {
|
||||
WP_CLI::error( __( 'The WordPress AI plugin has no provider configured for text generation.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
// Validate locale before AI checks so the user sees the right error when
|
||||
// both the locale is missing and the AI provider is unconfigured.
|
||||
if ( ! in_array( $locale, $this->translator->get_installed_locales(), true ) ) {
|
||||
WP_CLI::error(
|
||||
sprintf(
|
||||
|
|
@ -147,9 +136,22 @@ if ( ! class_exists( 'AI_Translator_CLI' ) ) {
|
|||
);
|
||||
}
|
||||
|
||||
$current_user = wp_get_current_user();
|
||||
if ( ! $current_user || 0 === (int) $current_user->ID ) {
|
||||
WP_CLI::warning( __( 'No user context. Pass --user=<id|login> so post updates carry an author and capability checks apply.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
if ( ! $dry_run && ! $this->translator->is_available() ) {
|
||||
WP_CLI::error( __( 'AI features are disabled for this WordPress installation.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
if ( ! $dry_run && ! $this->translator->is_supported() ) {
|
||||
WP_CLI::error( __( 'No AI provider is configured for text generation. Open Settings → AI to set one up.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
$settings = $this->settings->get_settings();
|
||||
|
||||
if ( empty( $settings['translate_title'] ) && empty( $settings['translate_content'] ) ) {
|
||||
if ( empty( $settings['translate_title'] ) && empty( $settings['translate_content'] ) && empty( $settings['translate_excerpt'] ) ) {
|
||||
WP_CLI::error( __( 'No translation fields are enabled in the plugin settings.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
|
|
@ -212,6 +214,16 @@ if ( ! class_exists( 'AI_Translator_CLI' ) ) {
|
|||
$update['post_content'] = $translated_content;
|
||||
}
|
||||
|
||||
if ( ! empty( $settings['translate_excerpt'] ) && post_type_supports( $post->post_type, 'excerpt' ) ) {
|
||||
$translated_excerpt = $this->translator->translate( $post->post_excerpt, $locale );
|
||||
if ( is_wp_error( $translated_excerpt ) ) {
|
||||
WP_CLI::warning( sprintf( '#%d excerpt: %s', $post->ID, $translated_excerpt->get_error_message() ) );
|
||||
++$failures;
|
||||
continue;
|
||||
}
|
||||
$update['post_excerpt'] = $translated_excerpt;
|
||||
}
|
||||
|
||||
$result = wp_update_post( $update, true );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
|||
* @since 1.0.0
|
||||
* @var array<int,string>
|
||||
*/
|
||||
const ALLOWED_FIELDS = array( 'title', 'content' );
|
||||
const ALLOWED_FIELDS = array( 'title', 'content', 'excerpt' );
|
||||
|
||||
/**
|
||||
* Settings handler.
|
||||
|
|
@ -210,6 +210,10 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
|||
if ( in_array( 'content', $fields, true ) && ! empty( $site_settings['translate_content'] ) ) {
|
||||
$allowed[] = 'content';
|
||||
}
|
||||
if ( in_array( 'excerpt', $fields, true ) && ! empty( $site_settings['translate_excerpt'] )
|
||||
&& post_type_supports( $post->post_type, 'excerpt' ) ) {
|
||||
$allowed[] = 'excerpt';
|
||||
}
|
||||
|
||||
if ( empty( $allowed ) ) {
|
||||
return new WP_Error(
|
||||
|
|
@ -237,6 +241,14 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
|||
$response['content'] = $translated;
|
||||
}
|
||||
|
||||
if ( in_array( 'excerpt', $allowed, true ) ) {
|
||||
$translated = $this->translator->translate( $post->post_excerpt, $target_locale );
|
||||
if ( is_wp_error( $translated ) ) {
|
||||
return $this->error_with_status( $translated, 502 );
|
||||
}
|
||||
$response['excerpt'] = $translated;
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $response, 200 );
|
||||
}
|
||||
|
||||
|
|
@ -304,8 +316,8 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
|||
?>
|
||||
<div id="ai-translator-classic" class="ai-translator-classic" data-post-id="<?php echo esc_attr( (string) $post->ID ); ?>">
|
||||
<?php if ( ! $this->translator->is_available() ) : ?>
|
||||
<p><?php echo esc_html__( 'The WordPress AI plugin is not active. Translation is unavailable.', 'robotstxt-ai-translator' ); ?></p>
|
||||
<?php elseif ( empty( $settings['translate_title'] ) && empty( $settings['translate_content'] ) ) : ?>
|
||||
<p><?php echo esc_html__( 'AI features are disabled for this WordPress installation.', 'robotstxt-ai-translator' ); ?></p>
|
||||
<?php elseif ( empty( $settings['translate_title'] ) && empty( $settings['translate_content'] ) && empty( $settings['translate_excerpt'] ) ) : ?>
|
||||
<p><?php echo esc_html__( 'No translation fields are enabled in settings.', 'robotstxt-ai-translator' ); ?></p>
|
||||
<?php elseif ( empty( $languages ) ) : ?>
|
||||
<p><?php echo esc_html__( 'No languages are installed on this site.', 'robotstxt-ai-translator' ); ?></p>
|
||||
|
|
@ -425,6 +437,7 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
|||
'settings' => array(
|
||||
'translateTitle' => ! empty( $settings['translate_title'] ),
|
||||
'translateContent' => ! empty( $settings['translate_content'] ),
|
||||
'translateExcerpt' => ! empty( $settings['translate_excerpt'] ),
|
||||
),
|
||||
'i18n' => array(
|
||||
'translate' => __( 'Translate', 'robotstxt-ai-translator' ),
|
||||
|
|
@ -435,10 +448,11 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
|||
'genericError' => __( 'Translation failed.', 'robotstxt-ai-translator' ),
|
||||
'noLanguages' => __( 'No languages are installed on this site.', 'robotstxt-ai-translator' ),
|
||||
'noFields' => __( 'No translation fields are enabled in settings.', 'robotstxt-ai-translator' ),
|
||||
'aiUnavailable' => __( 'The WordPress AI plugin is not active.', 'robotstxt-ai-translator' ),
|
||||
'aiUnavailable' => __( 'AI features are disabled for this WordPress installation.', 'robotstxt-ai-translator' ),
|
||||
'saveBeforeWarn' => __( 'You have unsaved changes. Save the post before translating to ensure the latest content is used.', 'robotstxt-ai-translator' ),
|
||||
'translateTitle' => __( 'Translate title', 'robotstxt-ai-translator' ),
|
||||
'translateContent' => __( 'Translate content', 'robotstxt-ai-translator' ),
|
||||
'translateExcerpt' => __( 'Translate excerpt', 'robotstxt-ai-translator' ),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -466,9 +480,9 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
|||
return (int) $post->ID;
|
||||
}
|
||||
|
||||
if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$post_get = wp_unslash( $_GET['post'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return is_scalar( $post_get ) ? absint( $post_get ) : 0;
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['post'] ) && is_scalar( $_GET['post'] ) ) {
|
||||
return absint( (string) $_GET['post'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
174
includes/class-ai-translator-mlp-integration.php
Normal file
174
includes/class-ai-translator-mlp-integration.php
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
/**
|
||||
* MultilingualPress integration for AI Translator.
|
||||
*
|
||||
* When MultilingualPress creates a new connected post (i.e. the editor saves
|
||||
* a source post and MLP syncs it to another site for the first time), this
|
||||
* class translates the source post's fields into the target site's language
|
||||
* and writes the result back to the newly created remote post.
|
||||
*
|
||||
* The hook `multilingualpress.metabox_after_update_remote_post` fires while
|
||||
* WordPress is already switched to the remote site, so wp_update_post() can
|
||||
* be called directly without an additional switch_to_blog().
|
||||
*
|
||||
* This integration only activates when:
|
||||
* 1. MultilingualPress is active (class_exists guard in bootstrap).
|
||||
* 2. "Auto-translate on MLP sync" is enabled in the plugin settings.
|
||||
* 3. An AI provider is configured for text generation.
|
||||
*
|
||||
* @package ROBOTSTXT\AI_Translator
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'AI_Translator_MLP_Integration' ) ) {
|
||||
|
||||
/**
|
||||
* Hooks into MultilingualPress post synchronization to auto-translate
|
||||
* newly connected posts.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
class AI_Translator_MLP_Integration {
|
||||
|
||||
/**
|
||||
* Settings handler.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @var AI_Translator_Settings
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* Translator handler.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @var AI_Translator_Translator
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*
|
||||
* @param AI_Translator_Settings $settings Settings handler.
|
||||
* @param AI_Translator_Translator $translator Translator handler.
|
||||
*/
|
||||
public function __construct( AI_Translator_Settings $settings, AI_Translator_Translator $translator ) {
|
||||
$this->settings = $settings;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers WordPress hooks.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
add_action(
|
||||
'multilingualpress.metabox_after_update_remote_post',
|
||||
array( $this, 'on_mlp_after_update_remote_post' ),
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the source post and updates the newly created remote post.
|
||||
*
|
||||
* Fires while WordPress is switched to the remote site, so
|
||||
* wp_update_post() can be called directly.
|
||||
*
|
||||
* Only acts when:
|
||||
* - $operation is 'new' (first connection, not an update).
|
||||
* - The "auto_translate_on_mlp_create" setting is enabled.
|
||||
* - An AI provider is configured for text generation.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*
|
||||
* @param \Inpsyde\MultilingualPress\TranslationUi\Post\RelationshipContext $context MLP relationship context.
|
||||
* @param array<string,scalar|null> $post Post data used to create the remote post.
|
||||
* @param string $operation 'new' = first connection, 'leave' = update.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function on_mlp_after_update_remote_post( $context, $post, $operation ) {
|
||||
unset( $post );
|
||||
|
||||
if ( 'new' !== $operation ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = $this->settings->get_settings();
|
||||
|
||||
if ( empty( $settings['auto_translate_on_mlp_create'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->translator->is_supported() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We are already on the remote site (MLP switched before firing this hook).
|
||||
// get_blog_option() with the explicit site ID is safe and avoids relying
|
||||
// on locale globals that may not be updated after switch_to_blog().
|
||||
$raw_locale = get_blog_option( $context->remoteSiteId(), 'WPLANG' );
|
||||
$target_locale = is_string( $raw_locale ) && '' !== $raw_locale ? $raw_locale : 'en_US';
|
||||
|
||||
// sourcePost() performs switch_to_blog internally and restores afterward.
|
||||
$source_post = $context->sourcePost();
|
||||
|
||||
if ( ! $source_post instanceof WP_Post ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remote_post_id = $context->remotePostId();
|
||||
$update = array( 'ID' => $remote_post_id );
|
||||
$has_changes = false;
|
||||
|
||||
// ── Translate title ──────────────────────────────────────────────
|
||||
|
||||
if ( ! empty( $settings['translate_title'] ) && '' !== trim( $source_post->post_title ) ) {
|
||||
$translated = $this->translator->translate( $source_post->post_title, $target_locale );
|
||||
if ( ! is_wp_error( $translated ) && '' !== $translated ) {
|
||||
$update['post_title'] = $translated;
|
||||
$has_changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Translate content ────────────────────────────────────────────
|
||||
|
||||
if ( ! empty( $settings['translate_content'] ) && '' !== trim( $source_post->post_content ) ) {
|
||||
$translated = $this->translator->translate( $source_post->post_content, $target_locale );
|
||||
if ( ! is_wp_error( $translated ) && '' !== $translated ) {
|
||||
$update['post_content'] = $translated;
|
||||
$has_changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Translate excerpt ────────────────────────────────────────────
|
||||
|
||||
if ( ! empty( $settings['translate_excerpt'] )
|
||||
&& '' !== trim( $source_post->post_excerpt )
|
||||
&& post_type_supports( $source_post->post_type, 'excerpt' )
|
||||
) {
|
||||
$translated = $this->translator->translate( $source_post->post_excerpt, $target_locale );
|
||||
if ( ! is_wp_error( $translated ) && '' !== $translated ) {
|
||||
$update['post_excerpt'] = $translated;
|
||||
$has_changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $has_changes ) {
|
||||
// Already on the remote site — no switch_to_blog() needed.
|
||||
wp_update_post( wp_slash( $update ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,8 @@ if ( ! class_exists( 'AI_Translator_Settings' ) ) {
|
|||
const DEFAULTS = array(
|
||||
'translate_title' => true,
|
||||
'translate_content' => true,
|
||||
'translate_excerpt' => true,
|
||||
'auto_translate_on_mlp_create' => false,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -23,14 +23,18 @@ if ( ! class_exists( 'AI_Translator_Translator' ) ) {
|
|||
class AI_Translator_Translator {
|
||||
|
||||
/**
|
||||
* Indicates whether the WordPress AI client is available.
|
||||
* Indicates whether AI features are enabled for this WordPress installation.
|
||||
*
|
||||
* `wp_ai_client_prompt()` is a native WordPress 7.0 function — the API is always
|
||||
* present. This method checks `wp_supports_ai()`, which returns false only when the
|
||||
* `WP_AI_SUPPORT` constant is set to false or the `wp_supports_ai` filter disables it.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return bool True if the dependency is active.
|
||||
* @return bool True when WordPress AI is enabled for this installation.
|
||||
*/
|
||||
public function is_available() {
|
||||
return function_exists( 'wp_ai_client_prompt' );
|
||||
return wp_supports_ai();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -78,15 +82,15 @@ if ( ! class_exists( 'AI_Translator_Translator' ) ) {
|
|||
|
||||
if ( ! $this->is_available() ) {
|
||||
return new WP_Error(
|
||||
'ai_translator_unavailable',
|
||||
__( 'The WordPress AI client is not available. Install and activate the WordPress AI plugin.', 'robotstxt-ai-translator' )
|
||||
'ai_translator_disabled',
|
||||
__( 'AI features are disabled for this WordPress installation.', 'robotstxt-ai-translator' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $this->is_supported() ) {
|
||||
return new WP_Error(
|
||||
'ai_translator_unsupported',
|
||||
__( 'The configured AI providers cannot serve text generation requests. Open the WordPress AI settings to configure a provider.', 'robotstxt-ai-translator' )
|
||||
__( 'No AI provider is configured for text generation. Open Settings → AI to set one up.', 'robotstxt-ai-translator' )
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,279 +1,409 @@
|
|||
# Copyright (C) 2026 ROBOTSTXT
|
||||
# This file is distributed under the GPL-3.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: AI Translator (by ROBOTSTXT) 1.0.0\n"
|
||||
"Project-Id-Version: AI Translator (by ROBOTSTXT) 1.1.0\n"
|
||||
"Report-Msgid-Bugs-To: https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator\n"
|
||||
"Last-Translator: ROBOTSTXT <hello@robotstxt.es>\n"
|
||||
"Language-Team: Catalan\n"
|
||||
"Language: ca\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2026-05-23T10:07:42+00:00\n"
|
||||
"PO-Revision-Date: 2026-05-23 10:30+0000\n"
|
||||
"Language: ca\n"
|
||||
"X-Generator: hand-written\n"
|
||||
"X-Domain: robotstxt-ai-translator\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "AI Translator (by ROBOTSTXT)"
|
||||
msgstr "AI Translator (by ROBOTSTXT)"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Translate post titles and content from the editor using the native WordPress AI plugin as backend. Multisite-ready with global or per-site configuration."
|
||||
msgstr "Tradueix el títol i el contingut de les entrades des de l'editor utilitzant el connector natiu d'IA de WordPress com a backend. Compatible amb Multisite en mode global o per lloc."
|
||||
|
||||
#. Author of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "ROBOTSTXT"
|
||||
msgstr "ROBOTSTXT"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:112
|
||||
#: includes/class-ai-translator-admin.php:113
|
||||
#: includes/class-ai-translator-admin.php:130
|
||||
#: includes/class-ai-translator-admin.php:131
|
||||
#: includes/class-ai-translator-editor-ui.php:294
|
||||
#: includes/class-ai-translator-editor-ui.php:446
|
||||
msgid "AI Translator"
|
||||
msgstr "AI Translator"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:153
|
||||
#: includes/class-ai-translator-admin.php:171
|
||||
msgid "Settings"
|
||||
msgstr "Ajustaments"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:187
|
||||
#: includes/class-ai-translator-admin.php:287
|
||||
msgid "You do not have permission to access this page."
|
||||
msgstr "No tens permís per accedir a aquesta pàgina."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:196
|
||||
msgid "AI Translator Settings"
|
||||
msgstr "Ajustaments d'AI Translator"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:200
|
||||
msgid "Settings saved."
|
||||
msgstr "Ajustaments desats."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:211
|
||||
#: includes/class-ai-translator-admin.php:214
|
||||
#: includes/class-ai-translator-admin.php:330
|
||||
#: includes/class-ai-translator-admin.php:334
|
||||
msgid "Translation fields"
|
||||
msgstr "Camps a traduir"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:218
|
||||
#: includes/class-ai-translator-admin.php:338
|
||||
#: includes/class-ai-translator-editor-ui.php:453
|
||||
msgid "Translate title"
|
||||
msgstr "Tradueix el títol"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:223
|
||||
#: includes/class-ai-translator-admin.php:343
|
||||
#: includes/class-ai-translator-editor-ui.php:454
|
||||
msgid "Translate content"
|
||||
msgstr "Tradueix el contingut"
|
||||
|
||||
#. translators: 1: title default, 2: content default.
|
||||
#, php-format
|
||||
msgid "Network defaults: title %1$s, content %2$s."
|
||||
msgstr "Valors predeterminats de la xarxa: títol %1$s, contingut %2$s."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:237
|
||||
#: includes/class-ai-translator-admin.php:238
|
||||
#: includes/class-ai-translator-admin.php:239
|
||||
msgid "enabled"
|
||||
msgstr "activat"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:237
|
||||
#: includes/class-ai-translator-admin.php:238
|
||||
#: includes/class-ai-translator-admin.php:239
|
||||
msgid "disabled"
|
||||
msgstr "desactivat"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:296
|
||||
msgid "AI Translator Network Settings"
|
||||
msgstr "Ajustaments de xarxa d'AI Translator"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:300
|
||||
msgid "Network settings saved."
|
||||
msgstr "Ajustaments de xarxa desats."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:310
|
||||
#: includes/class-ai-translator-admin.php:313
|
||||
msgid "Configuration mode"
|
||||
msgstr "Mode de configuració"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:317
|
||||
msgid "Global configuration: a single setting applies to every site."
|
||||
msgstr "Configuració global: un sol ajustament s'aplica a tots els llocs."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:322
|
||||
msgid "Per-site configuration: each site can override these defaults."
|
||||
msgstr "Configuració per lloc: cada lloc pot sobreescriure aquests valors predeterminats."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:352
|
||||
msgid "In global mode this is the configuration for the whole network. In per-site mode these values are used as defaults for sites that have not been configured individually."
|
||||
msgstr "En mode global aquesta és la configuració per a tota la xarxa. En mode per lloc aquests valors s'utilitzen com a predeterminats per als llocs que no s'hagin configurat individualment."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:397
|
||||
#: includes/class-ai-translator-admin.php:429
|
||||
msgid "You do not have permission to perform this action."
|
||||
msgstr "No tens permís per dur a terme aquesta acció."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:473
|
||||
msgid "Recommended models"
|
||||
msgstr "Models recomanats"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:476
|
||||
msgid "This plugin does not call AI providers directly. Configure your preferred provider in the WordPress AI plugin settings. The table below is a guide to help you pick a model based on your translation use case."
|
||||
msgstr "Aquest connector no truca directament als proveïdors d'IA. Configura el teu proveïdor preferit als ajustaments del connector WordPress AI. La taula següent és una guia per ajudar-te a triar un model segons el teu cas d'ús de traducció."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:482
|
||||
msgid "Use case"
|
||||
msgstr "Cas d'ús"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:483
|
||||
msgid "Recommended model"
|
||||
msgstr "Model recomanat"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:484
|
||||
msgid "Why"
|
||||
msgstr "Per què"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:499
|
||||
msgid "These recommendations are based on public benchmarks and community feedback as of the plugin release date. They may change as providers update their models."
|
||||
msgstr "Aquestes recomanacions es basen en benchmarks públics i comentaris de la comunitat en la data de publicació del connector. Poden canviar a mesura que els proveïdors actualitzin els seus models."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:514
|
||||
msgid "European languages (ES, CA, FR, DE, IT, PT)"
|
||||
msgstr "Llengües europees (ES, CA, FR, DE, IT, PT)"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:515
|
||||
msgid "DeepL API Pro"
|
||||
msgstr "DeepL API Pro"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:516
|
||||
msgid "Best fluency and naturalness (92/100 in benchmarks). Formality control and custom glossaries."
|
||||
msgstr "La millor fluïdesa i naturalitat (92/100 en benchmarks). Control de formalitat i glossaris personalitzats."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:519
|
||||
msgid "Asian languages (ZH, JA, KO)"
|
||||
msgstr "Llengües asiàtiques (ZH, JA, KO)"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:520
|
||||
msgid "GPT-4o / GPT-5 or Claude Sonnet 4"
|
||||
msgstr "GPT-4o / GPT-5 o Claude Sonnet 4"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:521
|
||||
msgid "Better handling of implicit subjects, honorifics, and cultural references. DeepL falls behind here."
|
||||
msgstr "Millor gestió dels subjectes implícits, els honorífics i les referències culturals. DeepL queda enrere aquí."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:524
|
||||
msgid "Marketing / brand tone"
|
||||
msgstr "Màrqueting / to de marca"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:525
|
||||
msgid "Claude Sonnet 4 / Opus 4"
|
||||
msgstr "Claude Sonnet 4 / Opus 4"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:526
|
||||
msgid "Better preservation of tone, brand voice, and nuance. Ideal for creative copy."
|
||||
msgstr "Preserva millor el to, la veu de la marca i els matisos. Ideal per a copy creatiu."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:529
|
||||
msgid "Technical documentation / code"
|
||||
msgstr "Documentació tècnica / codi"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:530
|
||||
msgid "GPT-4o / GPT-5"
|
||||
msgstr "GPT-4o / GPT-5"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:531
|
||||
msgid "Higher accuracy with variables, structured formats, and technical terminology."
|
||||
msgstr "Major precisió amb variables, formats estructurats i terminologia tècnica."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:534
|
||||
msgid "Long documents (100+ pages)"
|
||||
msgstr "Documents llargs (més de 100 pàgines)"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:535
|
||||
msgid "Gemini 2.5 Pro"
|
||||
msgstr "Gemini 2.5 Pro"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:536
|
||||
msgid "1M token context window. Maintains terminological consistency across long texts."
|
||||
msgstr "Finestra de context d'1 milió de tokens. Manté la consistència terminològica en textos llargs."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:539
|
||||
msgid "High volume / low cost"
|
||||
msgstr "Alt volum / baix cost"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:540
|
||||
msgid "DeepSeek-V3"
|
||||
msgstr "DeepSeek-V3"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:541
|
||||
msgid "Quality comparable to GPT-5 at ~$0.14/M tokens (20-50x cheaper than Claude/GPT)."
|
||||
msgstr "Qualitat comparable a GPT-5 a ~0,14 $/M tokens (entre 20 i 50 vegades més barat que Claude/GPT)."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:544
|
||||
msgid "Rare / indigenous languages"
|
||||
msgstr "Llengües rares / indígenes"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:545
|
||||
msgid "Claude Sonnet or Taskade Translate (multi-model routing)"
|
||||
msgstr "Claude Sonnet o Taskade Translate (encaminament multimodel)"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:546
|
||||
msgid "Better coverage for uncommon language pairs."
|
||||
msgstr "Millor cobertura en parells de llengües poc habituals."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:549
|
||||
msgid "Maximum coverage (133+ languages)"
|
||||
msgstr "Cobertura màxima (més de 133 llengües)"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:550
|
||||
msgid "Google Cloud Translation"
|
||||
msgstr "Google Cloud Translation"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:551
|
||||
msgid "The most complete option, though with slightly lower quality on European languages."
|
||||
msgstr "L'opció més completa, tot i que amb una qualitat lleugerament inferior en llengües europees."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:120
|
||||
msgid "The --locale flag is required."
|
||||
msgstr "El paràmetre --locale és obligatori."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:124
|
||||
msgid "The --post-id and --post-type flags are mutually exclusive."
|
||||
msgstr "Els paràmetres --post-id i --post-type són mútuament excloents."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:141
|
||||
msgid "No user context. Pass --user=<id|login> so post updates carry an author and capability checks apply."
|
||||
msgstr "Sense context d'usuari. Passa --user=<id|login> perquè les actualitzacions d'entrades tinguin autor i s'apliquin les comprovacions de capacitats."
|
||||
|
||||
msgid "The WordPress AI plugin is not active."
|
||||
msgstr "El connector WordPress AI no està actiu."
|
||||
|
||||
msgid "The WordPress AI plugin has no provider configured for text generation."
|
||||
msgstr "El connector WordPress AI no té cap proveïdor configurat per a la generació de text."
|
||||
|
||||
#. translators: %s: locale code.
|
||||
#: includes/class-ai-translator-cli.php:133
|
||||
#, php-format
|
||||
msgid "The locale %s is not installed on this site."
|
||||
msgstr "La llengua %s no està instal·lada en aquest lloc."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:155
|
||||
msgid "No translation fields are enabled in the plugin settings."
|
||||
msgstr "No hi ha cap camp de traducció activat als ajustaments del connector."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:181
|
||||
msgid "No posts matched the given criteria."
|
||||
msgstr "Cap entrada coincideix amb els criteris donats."
|
||||
|
||||
#. translators: 1: success count, 2: failure count, 3: locale.
|
||||
#: includes/class-ai-translator-cli.php:242
|
||||
#, php-format
|
||||
msgid "%1$d translated, %2$d failed (locale: %3$s)."
|
||||
msgstr "%1$d traduïdes, %2$d amb errors (llengua: %3$s)."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:160
|
||||
msgid "Sorry, you are not allowed to edit this post."
|
||||
msgstr "Ho sentim, no tens permís per editar aquesta entrada."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:190
|
||||
msgid "Post not found."
|
||||
msgstr "Entrada no trobada."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:199
|
||||
msgid "The selected language is not installed on this site."
|
||||
msgstr "La llengua seleccionada no està instal·lada en aquest lloc."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:221
|
||||
msgid "No translatable fields were selected, or the requested fields are disabled in the settings."
|
||||
msgstr "No s'ha seleccionat cap camp traduïble, o els camps sol·licitats estan desactivats als ajustaments."
|
||||
|
||||
msgid "The WordPress AI plugin is not active. Translation is unavailable."
|
||||
msgstr "El connector WordPress AI no està actiu. La traducció no està disponible."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:321
|
||||
#: includes/class-ai-translator-editor-ui.php:450
|
||||
msgid "No translation fields are enabled in settings."
|
||||
msgstr "No hi ha cap camp de traducció activat als ajustaments."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:323
|
||||
#: includes/class-ai-translator-editor-ui.php:449
|
||||
msgid "No languages are installed on this site."
|
||||
msgstr "No hi ha cap llengua instal·lada en aquest lloc."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:326
|
||||
#: includes/class-ai-translator-editor-ui.php:445
|
||||
msgid "Target language"
|
||||
msgstr "Llengua de destinació"
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:336
|
||||
#: includes/class-ai-translator-editor-ui.php:443
|
||||
msgid "Translate"
|
||||
msgstr "Tradueix"
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:341
|
||||
msgid "Save the post before translating to ensure the latest content is used. The translation replaces the current title and/or content."
|
||||
msgstr "Desa l'entrada abans de traduir perquè s'utilitzi la versió més recent. La traducció substitueix el títol i/o el contingut actuals."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:444
|
||||
msgid "Translating…"
|
||||
msgstr "Traduint…"
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:447
|
||||
msgid "Translation applied. Review and save the post."
|
||||
msgstr "Traducció aplicada. Revisa i desa l'entrada."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:448
|
||||
msgid "Translation failed."
|
||||
msgstr "La traducció ha fallat."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:452
|
||||
msgid "You have unsaved changes. Save the post before translating to ensure the latest content is used."
|
||||
msgstr "Tens canvis sense desar. Desa l'entrada abans de traduir perquè s'utilitzi la versió més recent."
|
||||
|
||||
msgid "The WordPress AI client is not available. Install and activate the WordPress AI plugin."
|
||||
msgstr "El client de WordPress AI no està disponible. Instal·la i activa el connector WordPress AI."
|
||||
|
||||
msgid "The configured AI providers cannot serve text generation requests. Open the WordPress AI settings to configure a provider."
|
||||
msgstr "Els proveïdors d'IA configurats no poden atendre peticions de generació de text. Obre els ajustaments de WordPress AI per configurar un proveïdor."
|
||||
|
||||
#: includes/class-ai-translator-translator.php:102
|
||||
msgid "The target language is not installed on this site."
|
||||
msgstr "La llengua de destinació no està instal·lada en aquest lloc."
|
||||
|
||||
#. translators: %s: target language name.
|
||||
#: includes/class-ai-translator-translator.php:108
|
||||
#, php-format
|
||||
msgid "You are a professional translator. Translate the user message into %s. Preserve any HTML, shortcodes, line breaks, and Markdown exactly as they appear. Return only the translated text, without explanations, prefaces, or quotation marks."
|
||||
msgstr "Ets un traductor professional. Tradueix el missatge de l'usuari al %s. Preserva l'HTML, els shortcodes, els salts de línia i el Markdown exactament com apareixen. Retorna només el text traduït, sense explicacions, prefacis ni cometes."
|
||||
|
||||
#: includes/class-ai-translator-translator.php:129
|
||||
msgid "The AI service returned an unexpected response."
|
||||
msgstr "El servei d'IA ha retornat una resposta inesperada."
|
||||
|
||||
#. translators: %s: WordPress AI plugin link.
|
||||
#, php-format
|
||||
msgid "AI Translator (by ROBOTSTXT) requires the %s plugin to be active. Translation features will be disabled until it is installed and configured."
|
||||
msgstr "AI Translator (by ROBOTSTXT) necessita que el connector %s estigui actiu. Les funcions de traducció estaran desactivades fins que s'instal·li i es configuri."
|
||||
|
||||
msgid "WordPress AI"
|
||||
msgstr "WordPress AI"
|
||||
|
||||
msgid "Security check failed"
|
||||
msgstr "Comprovació de seguretat fallida"
|
||||
|
||||
#: class-robotstxt-updater.php:456
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "No tens permisos suficients per accedir a aquesta pàgina."
|
||||
|
||||
#. Plugin URI of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator"
|
||||
msgstr ""
|
||||
|
||||
#. Description of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "Translate post titles and content from the editor using the native WordPress AI client (available in WP 7.0+). Multisite-ready with global or per-site configuration."
|
||||
msgstr ""
|
||||
|
||||
#. Author URI of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "https://www.robotstxt.es/"
|
||||
msgstr ""
|
||||
|
||||
#: class-robotstxt-updater.php:452
|
||||
msgid "Security check failed."
|
||||
msgstr "Error de verificació de seguretat."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:228
|
||||
#: includes/class-ai-translator-admin.php:348
|
||||
#: includes/class-ai-translator-editor-ui.php:455
|
||||
msgid "Translate excerpt"
|
||||
msgstr "Traduir l'extracte"
|
||||
|
||||
#. translators: 1: title default, 2: content default, 3: excerpt default.
|
||||
#: includes/class-ai-translator-admin.php:236
|
||||
#, php-format
|
||||
msgid "Network defaults: title %1$s, content %2$s, excerpt %3$s."
|
||||
msgstr "Valors predeterminats de la xarxa: títol %1$s, contingut %2$s, extracte %3$s."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:250
|
||||
#: includes/class-ai-translator-admin.php:253
|
||||
#: includes/class-ai-translator-admin.php:360
|
||||
#: includes/class-ai-translator-admin.php:363
|
||||
msgid "MultilingualPress"
|
||||
msgstr "MultilingualPress"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:257
|
||||
#: includes/class-ai-translator-admin.php:367
|
||||
msgid "Auto-translate new connected posts"
|
||||
msgstr "Traduir automàticament les entrades connectades noves"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:261
|
||||
#: includes/class-ai-translator-admin.php:371
|
||||
msgid "When MultilingualPress creates a new connected post, automatically translate its enabled fields into the target site's language. The translation runs synchronously during save."
|
||||
msgstr "Quan MultilingualPress crea una entrada connectada nova, tradueix automàticament els camps activats a l'idioma del lloc de destinació. La traducció s'executa de manera síncrona en desar."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:145
|
||||
#: includes/class-ai-translator-editor-ui.php:319
|
||||
#: includes/class-ai-translator-editor-ui.php:451
|
||||
#: includes/class-ai-translator-translator.php:86
|
||||
msgid "AI features are disabled for this WordPress installation."
|
||||
msgstr "Les funcions d'IA estan desactivades per a aquesta instal·lació de WordPress."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:149
|
||||
#: includes/class-ai-translator-translator.php:93
|
||||
msgid "No AI provider is configured for text generation. Open Settings → AI to set one up."
|
||||
msgstr "No hi ha cap proveïdor d'IA configurat per a la generació de text. Obre Ajustos → IA per configurar-ne un."
|
||||
|
||||
#. translators: %s: Link to the WordPress AI settings page.
|
||||
#: robotstxt-ai-translator.php:144
|
||||
#, php-format
|
||||
msgid "AI Translator (by ROBOTSTXT) requires a configured AI provider for text generation. Open %s to set one up."
|
||||
msgstr "AI Translator (by ROBOTSTXT) requereix un proveïdor d'IA configurat per a la generació de text. Obre %s per configurar-ne un."
|
||||
|
||||
#: robotstxt-ai-translator.php:145
|
||||
msgid "Settings → AI"
|
||||
msgstr "Ajustos → IA"
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,279 +1,409 @@
|
|||
# Copyright (C) 2026 ROBOTSTXT
|
||||
# This file is distributed under the GPL-3.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: AI Translator (by ROBOTSTXT) 1.0.0\n"
|
||||
"Project-Id-Version: AI Translator (by ROBOTSTXT) 1.1.0\n"
|
||||
"Report-Msgid-Bugs-To: https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator\n"
|
||||
"Last-Translator: ROBOTSTXT <hello@robotstxt.es>\n"
|
||||
"Language-Team: Spanish (Spain)\n"
|
||||
"Language: es_ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2026-05-23T10:07:42+00:00\n"
|
||||
"PO-Revision-Date: 2026-05-23 10:30+0000\n"
|
||||
"Language: es_ES\n"
|
||||
"X-Generator: hand-written\n"
|
||||
"X-Domain: robotstxt-ai-translator\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "AI Translator (by ROBOTSTXT)"
|
||||
msgstr "AI Translator (by ROBOTSTXT)"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Translate post titles and content from the editor using the native WordPress AI plugin as backend. Multisite-ready with global or per-site configuration."
|
||||
msgstr "Traduce el título y el contenido de las entradas desde el editor usando el plugin nativo de IA de WordPress como backend. Compatible con Multisitio en modo global o por sitio."
|
||||
|
||||
#. Author of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "ROBOTSTXT"
|
||||
msgstr "ROBOTSTXT"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:112
|
||||
#: includes/class-ai-translator-admin.php:113
|
||||
#: includes/class-ai-translator-admin.php:130
|
||||
#: includes/class-ai-translator-admin.php:131
|
||||
#: includes/class-ai-translator-editor-ui.php:294
|
||||
#: includes/class-ai-translator-editor-ui.php:446
|
||||
msgid "AI Translator"
|
||||
msgstr "AI Translator"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:153
|
||||
#: includes/class-ai-translator-admin.php:171
|
||||
msgid "Settings"
|
||||
msgstr "Ajustes"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:187
|
||||
#: includes/class-ai-translator-admin.php:287
|
||||
msgid "You do not have permission to access this page."
|
||||
msgstr "No tienes permiso para acceder a esta página."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:196
|
||||
msgid "AI Translator Settings"
|
||||
msgstr "Ajustes de AI Translator"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:200
|
||||
msgid "Settings saved."
|
||||
msgstr "Ajustes guardados."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:211
|
||||
#: includes/class-ai-translator-admin.php:214
|
||||
#: includes/class-ai-translator-admin.php:330
|
||||
#: includes/class-ai-translator-admin.php:334
|
||||
msgid "Translation fields"
|
||||
msgstr "Campos a traducir"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:218
|
||||
#: includes/class-ai-translator-admin.php:338
|
||||
#: includes/class-ai-translator-editor-ui.php:453
|
||||
msgid "Translate title"
|
||||
msgstr "Traducir título"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:223
|
||||
#: includes/class-ai-translator-admin.php:343
|
||||
#: includes/class-ai-translator-editor-ui.php:454
|
||||
msgid "Translate content"
|
||||
msgstr "Traducir contenido"
|
||||
|
||||
#. translators: 1: title default, 2: content default.
|
||||
#, php-format
|
||||
msgid "Network defaults: title %1$s, content %2$s."
|
||||
msgstr "Valores por defecto de la red: título %1$s, contenido %2$s."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:237
|
||||
#: includes/class-ai-translator-admin.php:238
|
||||
#: includes/class-ai-translator-admin.php:239
|
||||
msgid "enabled"
|
||||
msgstr "activado"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:237
|
||||
#: includes/class-ai-translator-admin.php:238
|
||||
#: includes/class-ai-translator-admin.php:239
|
||||
msgid "disabled"
|
||||
msgstr "desactivado"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:296
|
||||
msgid "AI Translator Network Settings"
|
||||
msgstr "Ajustes de red de AI Translator"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:300
|
||||
msgid "Network settings saved."
|
||||
msgstr "Ajustes de red guardados."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:310
|
||||
#: includes/class-ai-translator-admin.php:313
|
||||
msgid "Configuration mode"
|
||||
msgstr "Modo de configuración"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:317
|
||||
msgid "Global configuration: a single setting applies to every site."
|
||||
msgstr "Configuración global: un único ajuste se aplica a todos los sitios."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:322
|
||||
msgid "Per-site configuration: each site can override these defaults."
|
||||
msgstr "Configuración por sitio: cada sitio puede sobrescribir estos valores por defecto."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:352
|
||||
msgid "In global mode this is the configuration for the whole network. In per-site mode these values are used as defaults for sites that have not been configured individually."
|
||||
msgstr "En modo global esta es la configuración para toda la red. En modo por sitio estos valores se usan como predeterminados para los sitios que no se hayan configurado individualmente."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:397
|
||||
#: includes/class-ai-translator-admin.php:429
|
||||
msgid "You do not have permission to perform this action."
|
||||
msgstr "No tienes permiso para realizar esta acción."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:473
|
||||
msgid "Recommended models"
|
||||
msgstr "Modelos recomendados"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:476
|
||||
msgid "This plugin does not call AI providers directly. Configure your preferred provider in the WordPress AI plugin settings. The table below is a guide to help you pick a model based on your translation use case."
|
||||
msgstr "Este plugin no llama directamente a los proveedores de IA. Configura tu proveedor preferido en los ajustes del plugin WordPress AI. La tabla siguiente es una guía para ayudarte a elegir un modelo según tu caso de uso de traducción."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:482
|
||||
msgid "Use case"
|
||||
msgstr "Caso de uso"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:483
|
||||
msgid "Recommended model"
|
||||
msgstr "Modelo recomendado"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:484
|
||||
msgid "Why"
|
||||
msgstr "Por qué"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:499
|
||||
msgid "These recommendations are based on public benchmarks and community feedback as of the plugin release date. They may change as providers update their models."
|
||||
msgstr "Estas recomendaciones se basan en benchmarks públicos y comentarios de la comunidad en la fecha de publicación del plugin. Pueden cambiar a medida que los proveedores actualicen sus modelos."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:514
|
||||
msgid "European languages (ES, CA, FR, DE, IT, PT)"
|
||||
msgstr "Idiomas europeos (ES, CA, FR, DE, IT, PT)"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:515
|
||||
msgid "DeepL API Pro"
|
||||
msgstr "DeepL API Pro"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:516
|
||||
msgid "Best fluency and naturalness (92/100 in benchmarks). Formality control and custom glossaries."
|
||||
msgstr "La mejor fluidez y naturalidad (92/100 en benchmarks). Control de formalidad y glosarios personalizados."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:519
|
||||
msgid "Asian languages (ZH, JA, KO)"
|
||||
msgstr "Idiomas asiáticos (ZH, JA, KO)"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:520
|
||||
msgid "GPT-4o / GPT-5 or Claude Sonnet 4"
|
||||
msgstr "GPT-4o / GPT-5 o Claude Sonnet 4"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:521
|
||||
msgid "Better handling of implicit subjects, honorifics, and cultural references. DeepL falls behind here."
|
||||
msgstr "Mejor manejo de sujetos implícitos, honoríficos y referencias culturales. DeepL queda por detrás aquí."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:524
|
||||
msgid "Marketing / brand tone"
|
||||
msgstr "Marketing / tono de marca"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:525
|
||||
msgid "Claude Sonnet 4 / Opus 4"
|
||||
msgstr "Claude Sonnet 4 / Opus 4"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:526
|
||||
msgid "Better preservation of tone, brand voice, and nuance. Ideal for creative copy."
|
||||
msgstr "Preserva mejor el tono, la voz de marca y los matices. Ideal para copy creativo."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:529
|
||||
msgid "Technical documentation / code"
|
||||
msgstr "Documentación técnica / código"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:530
|
||||
msgid "GPT-4o / GPT-5"
|
||||
msgstr "GPT-4o / GPT-5"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:531
|
||||
msgid "Higher accuracy with variables, structured formats, and technical terminology."
|
||||
msgstr "Mayor precisión con variables, formatos estructurados y terminología técnica."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:534
|
||||
msgid "Long documents (100+ pages)"
|
||||
msgstr "Documentos largos (más de 100 páginas)"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:535
|
||||
msgid "Gemini 2.5 Pro"
|
||||
msgstr "Gemini 2.5 Pro"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:536
|
||||
msgid "1M token context window. Maintains terminological consistency across long texts."
|
||||
msgstr "Ventana de contexto de 1 millón de tokens. Mantiene la consistencia terminológica en textos extensos."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:539
|
||||
msgid "High volume / low cost"
|
||||
msgstr "Alto volumen / bajo coste"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:540
|
||||
msgid "DeepSeek-V3"
|
||||
msgstr "DeepSeek-V3"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:541
|
||||
msgid "Quality comparable to GPT-5 at ~$0.14/M tokens (20-50x cheaper than Claude/GPT)."
|
||||
msgstr "Calidad comparable a GPT-5 a ~0,14 $/M tokens (entre 20 y 50 veces más barato que Claude/GPT)."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:544
|
||||
msgid "Rare / indigenous languages"
|
||||
msgstr "Idiomas raros / indígenas"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:545
|
||||
msgid "Claude Sonnet or Taskade Translate (multi-model routing)"
|
||||
msgstr "Claude Sonnet o Taskade Translate (enrutado multimodelo)"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:546
|
||||
msgid "Better coverage for uncommon language pairs."
|
||||
msgstr "Mejor cobertura en pares de idiomas poco comunes."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:549
|
||||
msgid "Maximum coverage (133+ languages)"
|
||||
msgstr "Cobertura máxima (más de 133 idiomas)"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:550
|
||||
msgid "Google Cloud Translation"
|
||||
msgstr "Google Cloud Translation"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:551
|
||||
msgid "The most complete option, though with slightly lower quality on European languages."
|
||||
msgstr "La opción más completa, aunque con calidad ligeramente inferior en idiomas europeos."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:120
|
||||
msgid "The --locale flag is required."
|
||||
msgstr "El parámetro --locale es obligatorio."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:124
|
||||
msgid "The --post-id and --post-type flags are mutually exclusive."
|
||||
msgstr "Los parámetros --post-id y --post-type son mutuamente excluyentes."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:141
|
||||
msgid "No user context. Pass --user=<id|login> so post updates carry an author and capability checks apply."
|
||||
msgstr "Sin contexto de usuario. Pasa --user=<id|login> para que las actualizaciones de entradas tengan autor y se apliquen los controles de capacidades."
|
||||
|
||||
msgid "The WordPress AI plugin is not active."
|
||||
msgstr "El plugin WordPress AI no está activo."
|
||||
|
||||
msgid "The WordPress AI plugin has no provider configured for text generation."
|
||||
msgstr "El plugin WordPress AI no tiene ningún proveedor configurado para generación de texto."
|
||||
|
||||
#. translators: %s: locale code.
|
||||
#: includes/class-ai-translator-cli.php:133
|
||||
#, php-format
|
||||
msgid "The locale %s is not installed on this site."
|
||||
msgstr "El idioma %s no está instalado en este sitio."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:155
|
||||
msgid "No translation fields are enabled in the plugin settings."
|
||||
msgstr "No hay ningún campo de traducción activado en los ajustes del plugin."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:181
|
||||
msgid "No posts matched the given criteria."
|
||||
msgstr "Ninguna entrada coincide con los criterios indicados."
|
||||
|
||||
#. translators: 1: success count, 2: failure count, 3: locale.
|
||||
#: includes/class-ai-translator-cli.php:242
|
||||
#, php-format
|
||||
msgid "%1$d translated, %2$d failed (locale: %3$s)."
|
||||
msgstr "%1$d traducidas, %2$d con fallos (idioma: %3$s)."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:160
|
||||
msgid "Sorry, you are not allowed to edit this post."
|
||||
msgstr "Lo sentimos, no tienes permiso para editar esta entrada."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:190
|
||||
msgid "Post not found."
|
||||
msgstr "Entrada no encontrada."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:199
|
||||
msgid "The selected language is not installed on this site."
|
||||
msgstr "El idioma seleccionado no está instalado en este sitio."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:221
|
||||
msgid "No translatable fields were selected, or the requested fields are disabled in the settings."
|
||||
msgstr "No se ha seleccionado ningún campo traducible, o los campos solicitados están desactivados en los ajustes."
|
||||
|
||||
msgid "The WordPress AI plugin is not active. Translation is unavailable."
|
||||
msgstr "El plugin WordPress AI no está activo. La traducción no está disponible."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:321
|
||||
#: includes/class-ai-translator-editor-ui.php:450
|
||||
msgid "No translation fields are enabled in settings."
|
||||
msgstr "No hay ningún campo de traducción activado en los ajustes."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:323
|
||||
#: includes/class-ai-translator-editor-ui.php:449
|
||||
msgid "No languages are installed on this site."
|
||||
msgstr "No hay ningún idioma instalado en este sitio."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:326
|
||||
#: includes/class-ai-translator-editor-ui.php:445
|
||||
msgid "Target language"
|
||||
msgstr "Idioma de destino"
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:336
|
||||
#: includes/class-ai-translator-editor-ui.php:443
|
||||
msgid "Translate"
|
||||
msgstr "Traducir"
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:341
|
||||
msgid "Save the post before translating to ensure the latest content is used. The translation replaces the current title and/or content."
|
||||
msgstr "Guarda la entrada antes de traducir para que se use la versión más reciente. La traducción reemplaza el título y/o el contenido actuales."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:444
|
||||
msgid "Translating…"
|
||||
msgstr "Traduciendo…"
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:447
|
||||
msgid "Translation applied. Review and save the post."
|
||||
msgstr "Traducción aplicada. Revisa y guarda la entrada."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:448
|
||||
msgid "Translation failed."
|
||||
msgstr "La traducción ha fallado."
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:452
|
||||
msgid "You have unsaved changes. Save the post before translating to ensure the latest content is used."
|
||||
msgstr "Tienes cambios sin guardar. Guarda la entrada antes de traducir para que se use la versión más reciente."
|
||||
|
||||
msgid "The WordPress AI client is not available. Install and activate the WordPress AI plugin."
|
||||
msgstr "El cliente de WordPress AI no está disponible. Instala y activa el plugin WordPress AI."
|
||||
|
||||
msgid "The configured AI providers cannot serve text generation requests. Open the WordPress AI settings to configure a provider."
|
||||
msgstr "Los proveedores de IA configurados no pueden atender peticiones de generación de texto. Abre los ajustes de WordPress AI para configurar un proveedor."
|
||||
|
||||
#: includes/class-ai-translator-translator.php:102
|
||||
msgid "The target language is not installed on this site."
|
||||
msgstr "El idioma de destino no está instalado en este sitio."
|
||||
|
||||
#. translators: %s: target language name.
|
||||
#: includes/class-ai-translator-translator.php:108
|
||||
#, php-format
|
||||
msgid "You are a professional translator. Translate the user message into %s. Preserve any HTML, shortcodes, line breaks, and Markdown exactly as they appear. Return only the translated text, without explanations, prefaces, or quotation marks."
|
||||
msgstr "Eres un traductor profesional. Traduce el mensaje del usuario al %s. Preserva el HTML, los shortcodes, los saltos de línea y el Markdown exactamente como aparecen. Devuelve solo el texto traducido, sin explicaciones, prefacios ni comillas."
|
||||
|
||||
#: includes/class-ai-translator-translator.php:129
|
||||
msgid "The AI service returned an unexpected response."
|
||||
msgstr "El servicio de IA ha devuelto una respuesta inesperada."
|
||||
|
||||
#. translators: %s: WordPress AI plugin link.
|
||||
#, php-format
|
||||
msgid "AI Translator (by ROBOTSTXT) requires the %s plugin to be active. Translation features will be disabled until it is installed and configured."
|
||||
msgstr "AI Translator (by ROBOTSTXT) necesita que el plugin %s esté activo. Las funciones de traducción estarán desactivadas hasta que se instale y configure."
|
||||
|
||||
msgid "WordPress AI"
|
||||
msgstr "WordPress AI"
|
||||
|
||||
msgid "Security check failed"
|
||||
msgstr "Comprobación de seguridad fallida"
|
||||
|
||||
#: class-robotstxt-updater.php:456
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "No tienes permisos suficientes para acceder a esta página."
|
||||
|
||||
#. Plugin URI of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator"
|
||||
msgstr ""
|
||||
|
||||
#. Description of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "Translate post titles and content from the editor using the native WordPress AI client (available in WP 7.0+). Multisite-ready with global or per-site configuration."
|
||||
msgstr ""
|
||||
|
||||
#. Author URI of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "https://www.robotstxt.es/"
|
||||
msgstr ""
|
||||
|
||||
#: class-robotstxt-updater.php:452
|
||||
msgid "Security check failed."
|
||||
msgstr "Error de verificación de seguridad."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:228
|
||||
#: includes/class-ai-translator-admin.php:348
|
||||
#: includes/class-ai-translator-editor-ui.php:455
|
||||
msgid "Translate excerpt"
|
||||
msgstr "Traducir extracto"
|
||||
|
||||
#. translators: 1: title default, 2: content default, 3: excerpt default.
|
||||
#: includes/class-ai-translator-admin.php:236
|
||||
#, php-format
|
||||
msgid "Network defaults: title %1$s, content %2$s, excerpt %3$s."
|
||||
msgstr "Valores predeterminados de la red: título %1$s, contenido %2$s, extracto %3$s."
|
||||
|
||||
#: includes/class-ai-translator-admin.php:250
|
||||
#: includes/class-ai-translator-admin.php:253
|
||||
#: includes/class-ai-translator-admin.php:360
|
||||
#: includes/class-ai-translator-admin.php:363
|
||||
msgid "MultilingualPress"
|
||||
msgstr "MultilingualPress"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:257
|
||||
#: includes/class-ai-translator-admin.php:367
|
||||
msgid "Auto-translate new connected posts"
|
||||
msgstr "Traducir automáticamente las entradas conectadas nuevas"
|
||||
|
||||
#: includes/class-ai-translator-admin.php:261
|
||||
#: includes/class-ai-translator-admin.php:371
|
||||
msgid "When MultilingualPress creates a new connected post, automatically translate its enabled fields into the target site's language. The translation runs synchronously during save."
|
||||
msgstr "Cuando MultilingualPress crea una entrada conectada nueva, traduce automáticamente los campos activados al idioma del sitio de destino. La traducción se ejecuta de forma síncrona al guardar."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:145
|
||||
#: includes/class-ai-translator-editor-ui.php:319
|
||||
#: includes/class-ai-translator-editor-ui.php:451
|
||||
#: includes/class-ai-translator-translator.php:86
|
||||
msgid "AI features are disabled for this WordPress installation."
|
||||
msgstr "Las funciones de IA están desactivadas para esta instalación de WordPress."
|
||||
|
||||
#: includes/class-ai-translator-cli.php:149
|
||||
#: includes/class-ai-translator-translator.php:93
|
||||
msgid "No AI provider is configured for text generation. Open Settings → AI to set one up."
|
||||
msgstr "No hay ningún proveedor de IA configurado para la generación de texto. Abre Ajustes → IA para configurar uno."
|
||||
|
||||
#. translators: %s: Link to the WordPress AI settings page.
|
||||
#: robotstxt-ai-translator.php:144
|
||||
#, php-format
|
||||
msgid "AI Translator (by ROBOTSTXT) requires a configured AI provider for text generation. Open %s to set one up."
|
||||
msgstr "AI Translator (by ROBOTSTXT) requiere un proveedor de IA configurado para la generación de texto. Abre %s para configurar uno."
|
||||
|
||||
#: robotstxt-ai-translator.php:145
|
||||
msgid "Settings → AI"
|
||||
msgstr "Ajustes → IA"
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
# This file is distributed under the GPL-3.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: AI Translator (by ROBOTSTXT) 1.0.0\n"
|
||||
"Project-Id-Version: AI Translator (by ROBOTSTXT) 1.1.0\n"
|
||||
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-ai-translator\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2026-05-23T10:07:42+00:00\n"
|
||||
"POT-Creation-Date: 2026-05-25T16:53:01+00:00\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"X-Generator: WP-CLI 2.12.0\n"
|
||||
"X-Domain: robotstxt-ai-translator\n"
|
||||
|
|
@ -26,7 +26,7 @@ msgstr ""
|
|||
|
||||
#. Description of the plugin
|
||||
#: robotstxt-ai-translator.php
|
||||
msgid "Translate post titles and content from the editor using the native WordPress AI plugin as backend. Multisite-ready with global or per-site configuration."
|
||||
msgid "Translate post titles and content from the editor using the native WordPress AI client (available in WP 7.0+). Multisite-ready with global or per-site configuration."
|
||||
msgstr ""
|
||||
|
||||
#. Author of the plugin
|
||||
|
|
@ -39,12 +39,20 @@ msgstr ""
|
|||
msgid "https://www.robotstxt.es/"
|
||||
msgstr ""
|
||||
|
||||
#: class-robotstxt-updater.php:452
|
||||
msgid "Security check failed."
|
||||
msgstr ""
|
||||
|
||||
#: class-robotstxt-updater.php:456
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:112
|
||||
#: includes/class-ai-translator-admin.php:113
|
||||
#: includes/class-ai-translator-admin.php:130
|
||||
#: includes/class-ai-translator-admin.php:131
|
||||
#: includes/class-ai-translator-editor-ui.php:282
|
||||
#: includes/class-ai-translator-editor-ui.php:433
|
||||
#: includes/class-ai-translator-editor-ui.php:294
|
||||
#: includes/class-ai-translator-editor-ui.php:446
|
||||
msgid "AI Translator"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -54,7 +62,7 @@ msgid "Settings"
|
|||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:187
|
||||
#: includes/class-ai-translator-admin.php:261
|
||||
#: includes/class-ai-translator-admin.php:287
|
||||
msgid "You do not have permission to access this page."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -68,186 +76,211 @@ msgstr ""
|
|||
|
||||
#: includes/class-ai-translator-admin.php:211
|
||||
#: includes/class-ai-translator-admin.php:214
|
||||
#: includes/class-ai-translator-admin.php:304
|
||||
#: includes/class-ai-translator-admin.php:308
|
||||
#: includes/class-ai-translator-admin.php:330
|
||||
#: includes/class-ai-translator-admin.php:334
|
||||
msgid "Translation fields"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:218
|
||||
#: includes/class-ai-translator-admin.php:312
|
||||
#: includes/class-ai-translator-editor-ui.php:440
|
||||
#: includes/class-ai-translator-admin.php:338
|
||||
#: includes/class-ai-translator-editor-ui.php:453
|
||||
msgid "Translate title"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:223
|
||||
#: includes/class-ai-translator-admin.php:317
|
||||
#: includes/class-ai-translator-editor-ui.php:441
|
||||
#: includes/class-ai-translator-admin.php:343
|
||||
#: includes/class-ai-translator-editor-ui.php:454
|
||||
msgid "Translate content"
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: title default, 2: content default.
|
||||
#: includes/class-ai-translator-admin.php:231
|
||||
#, php-format
|
||||
msgid "Network defaults: title %1$s, content %2$s."
|
||||
#: includes/class-ai-translator-admin.php:228
|
||||
#: includes/class-ai-translator-admin.php:348
|
||||
#: includes/class-ai-translator-editor-ui.php:455
|
||||
msgid "Translate excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:232
|
||||
#: includes/class-ai-translator-admin.php:233
|
||||
#. translators: 1: title default, 2: content default, 3: excerpt default.
|
||||
#: includes/class-ai-translator-admin.php:236
|
||||
#, php-format
|
||||
msgid "Network defaults: title %1$s, content %2$s, excerpt %3$s."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:237
|
||||
#: includes/class-ai-translator-admin.php:238
|
||||
#: includes/class-ai-translator-admin.php:239
|
||||
msgid "enabled"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:232
|
||||
#: includes/class-ai-translator-admin.php:233
|
||||
#: includes/class-ai-translator-admin.php:237
|
||||
#: includes/class-ai-translator-admin.php:238
|
||||
#: includes/class-ai-translator-admin.php:239
|
||||
msgid "disabled"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:270
|
||||
msgid "AI Translator Network Settings"
|
||||
#: includes/class-ai-translator-admin.php:250
|
||||
#: includes/class-ai-translator-admin.php:253
|
||||
#: includes/class-ai-translator-admin.php:360
|
||||
#: includes/class-ai-translator-admin.php:363
|
||||
msgid "MultilingualPress"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:274
|
||||
msgid "Network settings saved."
|
||||
#: includes/class-ai-translator-admin.php:257
|
||||
#: includes/class-ai-translator-admin.php:367
|
||||
msgid "Auto-translate new connected posts"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:284
|
||||
#: includes/class-ai-translator-admin.php:287
|
||||
msgid "Configuration mode"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:291
|
||||
msgid "Global configuration: a single setting applies to every site."
|
||||
#: includes/class-ai-translator-admin.php:261
|
||||
#: includes/class-ai-translator-admin.php:371
|
||||
msgid "When MultilingualPress creates a new connected post, automatically translate its enabled fields into the target site's language. The translation runs synchronously during save."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:296
|
||||
msgid "AI Translator Network Settings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:300
|
||||
msgid "Network settings saved."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:310
|
||||
#: includes/class-ai-translator-admin.php:313
|
||||
msgid "Configuration mode"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:317
|
||||
msgid "Global configuration: a single setting applies to every site."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:322
|
||||
msgid "Per-site configuration: each site can override these defaults."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:321
|
||||
#: includes/class-ai-translator-admin.php:352
|
||||
msgid "In global mode this is the configuration for the whole network. In per-site mode these values are used as defaults for sites that have not been configured individually."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:346
|
||||
#: includes/class-ai-translator-admin.php:378
|
||||
#: includes/class-ai-translator-admin.php:397
|
||||
#: includes/class-ai-translator-admin.php:429
|
||||
msgid "You do not have permission to perform this action."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:422
|
||||
#: includes/class-ai-translator-admin.php:473
|
||||
msgid "Recommended models"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:425
|
||||
#: includes/class-ai-translator-admin.php:476
|
||||
msgid "This plugin does not call AI providers directly. Configure your preferred provider in the WordPress AI plugin settings. The table below is a guide to help you pick a model based on your translation use case."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:431
|
||||
#: includes/class-ai-translator-admin.php:482
|
||||
msgid "Use case"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:432
|
||||
#: includes/class-ai-translator-admin.php:483
|
||||
msgid "Recommended model"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:433
|
||||
#: includes/class-ai-translator-admin.php:484
|
||||
msgid "Why"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:448
|
||||
#: includes/class-ai-translator-admin.php:499
|
||||
msgid "These recommendations are based on public benchmarks and community feedback as of the plugin release date. They may change as providers update their models."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:463
|
||||
#: includes/class-ai-translator-admin.php:514
|
||||
msgid "European languages (ES, CA, FR, DE, IT, PT)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:464
|
||||
#: includes/class-ai-translator-admin.php:515
|
||||
msgid "DeepL API Pro"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:465
|
||||
#: includes/class-ai-translator-admin.php:516
|
||||
msgid "Best fluency and naturalness (92/100 in benchmarks). Formality control and custom glossaries."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:468
|
||||
#: includes/class-ai-translator-admin.php:519
|
||||
msgid "Asian languages (ZH, JA, KO)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:469
|
||||
#: includes/class-ai-translator-admin.php:520
|
||||
msgid "GPT-4o / GPT-5 or Claude Sonnet 4"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:470
|
||||
#: includes/class-ai-translator-admin.php:521
|
||||
msgid "Better handling of implicit subjects, honorifics, and cultural references. DeepL falls behind here."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:473
|
||||
#: includes/class-ai-translator-admin.php:524
|
||||
msgid "Marketing / brand tone"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:474
|
||||
#: includes/class-ai-translator-admin.php:525
|
||||
msgid "Claude Sonnet 4 / Opus 4"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:475
|
||||
#: includes/class-ai-translator-admin.php:526
|
||||
msgid "Better preservation of tone, brand voice, and nuance. Ideal for creative copy."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:478
|
||||
#: includes/class-ai-translator-admin.php:529
|
||||
msgid "Technical documentation / code"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:479
|
||||
#: includes/class-ai-translator-admin.php:530
|
||||
msgid "GPT-4o / GPT-5"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:480
|
||||
#: includes/class-ai-translator-admin.php:531
|
||||
msgid "Higher accuracy with variables, structured formats, and technical terminology."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:483
|
||||
#: includes/class-ai-translator-admin.php:534
|
||||
msgid "Long documents (100+ pages)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:484
|
||||
#: includes/class-ai-translator-admin.php:535
|
||||
msgid "Gemini 2.5 Pro"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:485
|
||||
#: includes/class-ai-translator-admin.php:536
|
||||
msgid "1M token context window. Maintains terminological consistency across long texts."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:488
|
||||
#: includes/class-ai-translator-admin.php:539
|
||||
msgid "High volume / low cost"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:489
|
||||
#: includes/class-ai-translator-admin.php:540
|
||||
msgid "DeepSeek-V3"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:490
|
||||
#: includes/class-ai-translator-admin.php:541
|
||||
msgid "Quality comparable to GPT-5 at ~$0.14/M tokens (20-50x cheaper than Claude/GPT)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:493
|
||||
#: includes/class-ai-translator-admin.php:544
|
||||
msgid "Rare / indigenous languages"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:494
|
||||
#: includes/class-ai-translator-admin.php:545
|
||||
msgid "Claude Sonnet or Taskade Translate (multi-model routing)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:495
|
||||
#: includes/class-ai-translator-admin.php:546
|
||||
msgid "Better coverage for uncommon language pairs."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:498
|
||||
#: includes/class-ai-translator-admin.php:549
|
||||
msgid "Maximum coverage (133+ languages)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:499
|
||||
#: includes/class-ai-translator-admin.php:550
|
||||
msgid "Google Cloud Translation"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-admin.php:500
|
||||
#: includes/class-ai-translator-admin.php:551
|
||||
msgid "The most complete option, though with slightly lower quality on European languages."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -259,35 +292,38 @@ msgstr ""
|
|||
msgid "The --post-id and --post-type flags are mutually exclusive."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-cli.php:129
|
||||
msgid "No user context. Pass --user=<id|login> so post updates carry an author and capability checks apply."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-cli.php:133
|
||||
#: includes/class-ai-translator-editor-ui.php:438
|
||||
msgid "The WordPress AI plugin is not active."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-cli.php:137
|
||||
msgid "The WordPress AI plugin has no provider configured for text generation."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: locale code.
|
||||
#: includes/class-ai-translator-cli.php:144
|
||||
#: includes/class-ai-translator-cli.php:133
|
||||
#, php-format
|
||||
msgid "The locale %s is not installed on this site."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-cli.php:153
|
||||
#: includes/class-ai-translator-cli.php:141
|
||||
msgid "No user context. Pass --user=<id|login> so post updates carry an author and capability checks apply."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-cli.php:145
|
||||
#: includes/class-ai-translator-editor-ui.php:319
|
||||
#: includes/class-ai-translator-editor-ui.php:451
|
||||
#: includes/class-ai-translator-translator.php:86
|
||||
msgid "AI features are disabled for this WordPress installation."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-cli.php:149
|
||||
#: includes/class-ai-translator-translator.php:93
|
||||
msgid "No AI provider is configured for text generation. Open Settings → AI to set one up."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-cli.php:155
|
||||
msgid "No translation fields are enabled in the plugin settings."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-cli.php:179
|
||||
#: includes/class-ai-translator-cli.php:181
|
||||
msgid "No posts matched the given criteria."
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: success count, 2: failure count, 3: locale.
|
||||
#: includes/class-ai-translator-cli.php:230
|
||||
#: includes/class-ai-translator-cli.php:242
|
||||
#, php-format
|
||||
msgid "%1$d translated, %2$d failed (locale: %3$s)."
|
||||
msgstr ""
|
||||
|
|
@ -304,90 +340,70 @@ msgstr ""
|
|||
msgid "The selected language is not installed on this site."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:217
|
||||
#: includes/class-ai-translator-editor-ui.php:221
|
||||
msgid "No translatable fields were selected, or the requested fields are disabled in the settings."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:307
|
||||
msgid "The WordPress AI plugin is not active. Translation is unavailable."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:309
|
||||
#: includes/class-ai-translator-editor-ui.php:437
|
||||
#: includes/class-ai-translator-editor-ui.php:321
|
||||
#: includes/class-ai-translator-editor-ui.php:450
|
||||
msgid "No translation fields are enabled in settings."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:311
|
||||
#: includes/class-ai-translator-editor-ui.php:436
|
||||
#: includes/class-ai-translator-editor-ui.php:323
|
||||
#: includes/class-ai-translator-editor-ui.php:449
|
||||
msgid "No languages are installed on this site."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:314
|
||||
#: includes/class-ai-translator-editor-ui.php:432
|
||||
#: includes/class-ai-translator-editor-ui.php:326
|
||||
#: includes/class-ai-translator-editor-ui.php:445
|
||||
msgid "Target language"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:324
|
||||
#: includes/class-ai-translator-editor-ui.php:430
|
||||
#: includes/class-ai-translator-editor-ui.php:336
|
||||
#: includes/class-ai-translator-editor-ui.php:443
|
||||
msgid "Translate"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:329
|
||||
#: includes/class-ai-translator-editor-ui.php:341
|
||||
msgid "Save the post before translating to ensure the latest content is used. The translation replaces the current title and/or content."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:431
|
||||
#: includes/class-ai-translator-editor-ui.php:444
|
||||
msgid "Translating…"
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:434
|
||||
#: includes/class-ai-translator-editor-ui.php:447
|
||||
msgid "Translation applied. Review and save the post."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:435
|
||||
#: includes/class-ai-translator-editor-ui.php:448
|
||||
msgid "Translation failed."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-editor-ui.php:439
|
||||
#: includes/class-ai-translator-editor-ui.php:452
|
||||
msgid "You have unsaved changes. Save the post before translating to ensure the latest content is used."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-translator.php:82
|
||||
msgid "The WordPress AI client is not available. Install and activate the WordPress AI plugin."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-translator.php:89
|
||||
msgid "The configured AI providers cannot serve text generation requests. Open the WordPress AI settings to configure a provider."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-translator.php:98
|
||||
#: includes/class-ai-translator-translator.php:102
|
||||
msgid "The target language is not installed on this site."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: target language name.
|
||||
#: includes/class-ai-translator-translator.php:104
|
||||
#: includes/class-ai-translator-translator.php:108
|
||||
#, php-format
|
||||
msgid "You are a professional translator. Translate the user message into %s. Preserve any HTML, shortcodes, line breaks, and Markdown exactly as they appear. Return only the translated text, without explanations, prefaces, or quotation marks."
|
||||
msgstr ""
|
||||
|
||||
#: includes/class-ai-translator-translator.php:125
|
||||
#: includes/class-ai-translator-translator.php:129
|
||||
msgid "The AI service returned an unexpected response."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: WordPress AI plugin link.
|
||||
#: robotstxt-ai-translator.php:109
|
||||
#. translators: %s: Link to the WordPress AI settings page.
|
||||
#: robotstxt-ai-translator.php:144
|
||||
#, php-format
|
||||
msgid "AI Translator (by ROBOTSTXT) requires the %s plugin to be active. Translation features will be disabled until it is installed and configured."
|
||||
msgid "AI Translator (by ROBOTSTXT) requires a configured AI provider for text generation. Open %s to set one up."
|
||||
msgstr ""
|
||||
|
||||
#: robotstxt-ai-translator.php:110
|
||||
msgid "WordPress AI"
|
||||
msgstr ""
|
||||
|
||||
#: robotstxt-updater.php:362
|
||||
msgid "Security check failed"
|
||||
msgstr ""
|
||||
|
||||
#: robotstxt-updater.php:367
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
#: robotstxt-ai-translator.php:145
|
||||
msgid "Settings → AI"
|
||||
msgstr ""
|
||||
|
|
|
|||
22
readme.txt
22
readme.txt
|
|
@ -2,10 +2,9 @@
|
|||
Contributors: robotstxt, javiercasares
|
||||
Tags: ai, translation, multilingual, multisite, editor
|
||||
Requires at least: 7.0
|
||||
Tested up to: 7.0
|
||||
Tested up to: 7.1
|
||||
Requires PHP: 7.4
|
||||
Requires Plugins: ai
|
||||
Stable tag: 1.0.0
|
||||
Stable tag: 1.1.0
|
||||
License: GPL-3.0-or-later
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
|
||||
|
|
@ -109,9 +108,9 @@ The endpoint reads the **saved** title and content from the database, not from t
|
|||
|
||||
= Requirements =
|
||||
|
||||
* WordPress 7.0 or higher.
|
||||
* WordPress 7.0 or higher (the `wp_ai_client_prompt()` function is part of WordPress core since 7.0).
|
||||
* PHP 7.4 or higher.
|
||||
* The official WordPress AI plugin (https://wordpress.org/plugins/ai/) installed, active, and configured with valid provider credentials.
|
||||
* At least one AI provider configured in Settings → AI. The plugin works with any provider registered through the WordPress AI client — no specific AI plugin is required.
|
||||
|
||||
= Manual download =
|
||||
|
||||
|
|
@ -123,7 +122,7 @@ On Multisite, network-activate the plugin and configure it under Network Admin -
|
|||
|
||||
= Does this plugin call any AI provider directly? =
|
||||
|
||||
No. Every AI request is delegated to the official WordPress AI plugin, which handles authentication, model selection, and provider routing. This plugin does not store API keys.
|
||||
No. Every AI request goes through `wp_ai_client_prompt()`, WordPress's native AI client (introduced in WP 7.0). The plugin does not talk to any provider directly and does not store API keys. Authentication, model selection, and provider routing are handled by WordPress and whatever AI provider plugin you have configured.
|
||||
|
||||
= Does it work with the classic editor? =
|
||||
|
||||
|
|
@ -135,12 +134,19 @@ The plugin reads the post title and content from the database, not from the edit
|
|||
|
||||
== Compatibility ==
|
||||
|
||||
* WordPress: 7.0
|
||||
* WordPress: 7.0 - 7.1
|
||||
* PHP: 7.4 - 8.5
|
||||
* WP-CLI: 2.10 or newer
|
||||
* WP-CLI: 2.12 or newer
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.1.0 =
|
||||
|
||||
* Added excerpt field (`post_excerpt`) as a translatable field, with a dedicated settings toggle.
|
||||
* Added MultilingualPress integration: when MLP creates a new connected post, the plugin automatically translates the enabled fields into the target site's language (opt-in, disabled by default).
|
||||
* Removed `Requires Plugins: ai` header — `wp_ai_client_prompt()` is a native WordPress 7.0 function; the plugin now works with any AI provider configured through WordPress core.
|
||||
* Admin notice now shows when no AI provider is configured for text generation (instead of checking for a function that always exists in WP 7.0+).
|
||||
|
||||
= 1.0.0 =
|
||||
|
||||
* Initial release. Translation panel for block and classic editors, REST endpoint, WP-CLI command, single-site and Multisite (global / per-site) configuration modes.
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
* Plugin Name: AI Translator (by ROBOTSTXT)
|
||||
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator
|
||||
* Gitea Plugin URI: ROBOTSTXT/robotstxt-ai-translator
|
||||
* Description: Translate post titles and content from the editor using the native WordPress AI plugin as backend. Multisite-ready with global or per-site configuration.
|
||||
* Version: 1.0.0
|
||||
* Description: Translate post titles and content from the editor using the native WordPress AI client (available in WP 7.0+). Multisite-ready with global or per-site configuration.
|
||||
* Version: 1.1.0
|
||||
* Author: ROBOTSTXT
|
||||
* Author URI: https://www.robotstxt.es/
|
||||
* License: GPL-3.0-or-later
|
||||
|
|
@ -13,7 +13,6 @@
|
|||
* Domain Path: /languages
|
||||
* Requires at least: 7.0
|
||||
* Requires PHP: 7.4
|
||||
* Requires Plugins: ai
|
||||
* Network: true
|
||||
*
|
||||
* @package ROBOTSTXT\AI_Translator
|
||||
|
|
@ -26,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
/**
|
||||
* Plugin version.
|
||||
*/
|
||||
define( 'AI_TRANSLATOR_VERSION', '1.0.0' );
|
||||
define( 'AI_TRANSLATOR_VERSION', '1.1.0' );
|
||||
|
||||
/**
|
||||
* Main plugin file path.
|
||||
|
|
@ -57,7 +56,11 @@ require_once AI_TRANSLATOR_DIR . 'includes/class-ai-translator-settings.php';
|
|||
require_once AI_TRANSLATOR_DIR . 'includes/class-ai-translator-translator.php';
|
||||
require_once AI_TRANSLATOR_DIR . 'includes/class-ai-translator-admin.php';
|
||||
require_once AI_TRANSLATOR_DIR . 'includes/class-ai-translator-editor-ui.php';
|
||||
require_once AI_TRANSLATOR_DIR . 'robotstxt-updater.php';
|
||||
require_once AI_TRANSLATOR_DIR . 'class-robotstxt-updater.php';
|
||||
|
||||
if ( class_exists( '\Inpsyde\MultilingualPress\TranslationUi\Post\MetaboxAction' ) ) {
|
||||
require_once AI_TRANSLATOR_DIR . 'includes/class-ai-translator-mlp-integration.php';
|
||||
}
|
||||
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
require_once AI_TRANSLATOR_DIR . 'includes/class-ai-translator-cli.php';
|
||||
|
|
@ -103,30 +106,43 @@ function ai_translator_bootstrap() {
|
|||
$cli->register();
|
||||
}
|
||||
|
||||
if ( class_exists( 'AI_Translator_MLP_Integration' ) ) {
|
||||
$mlp = new AI_Translator_MLP_Integration( $settings, $translator );
|
||||
$mlp->register();
|
||||
}
|
||||
|
||||
Robotstxt_Updater::init( AI_TRANSLATOR_FILE );
|
||||
}
|
||||
add_action( 'plugins_loaded', 'ai_translator_bootstrap' );
|
||||
|
||||
/**
|
||||
* Renders an admin notice when the WordPress AI plugin is not active.
|
||||
* Renders an admin notice when no AI provider is configured for text generation.
|
||||
*
|
||||
* `wp_ai_client_prompt()` is part of WordPress 7.0 core, so the function always
|
||||
* exists. The meaningful check is whether a provider capable of text generation has
|
||||
* been configured — which is what `AI_Translator_Translator::is_supported()` tests.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function ai_translator_dependency_notice() {
|
||||
if ( function_exists( 'wp_ai_client_prompt' ) ) {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'activate_plugins' ) ) {
|
||||
$translator = new AI_Translator_Translator();
|
||||
|
||||
if ( $translator->is_supported() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings_url = admin_url( 'options-general.php?page=options-connectors-wp-admin' );
|
||||
|
||||
$message = sprintf(
|
||||
/* translators: %s: WordPress AI plugin link. */
|
||||
esc_html__( 'AI Translator (by ROBOTSTXT) requires the %s plugin to be active. Translation features will be disabled until it is installed and configured.', 'robotstxt-ai-translator' ),
|
||||
'<a href="https://wordpress.org/plugins/ai/" target="_blank" rel="noopener noreferrer">' . esc_html__( 'WordPress AI', 'robotstxt-ai-translator' ) . '</a>'
|
||||
/* translators: %s: Link to the WordPress AI settings page. */
|
||||
esc_html__( 'AI Translator (by ROBOTSTXT) requires a configured AI provider for text generation. Open %s to set one up.', 'robotstxt-ai-translator' ),
|
||||
'<a href="' . esc_url( $settings_url ) . '">' . esc_html__( 'Settings → AI', 'robotstxt-ai-translator' ) . '</a>'
|
||||
);
|
||||
|
||||
printf(
|
||||
|
|
@ -136,8 +152,6 @@ function ai_translator_dependency_notice() {
|
|||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'target' => array(),
|
||||
'rel' => array(),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,383 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Generic JSON-based updater for ROBOTSTXT plugins.
|
||||
*
|
||||
* This file is designed to be copied to any ROBOTSTXT plugin.
|
||||
* It auto-configures itself by reading the plugin headers.
|
||||
*
|
||||
* @package ROBOTSTXT
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'Robotstxt_Updater' ) ) {
|
||||
/**
|
||||
* Class Robotstxt_Updater
|
||||
*
|
||||
* Generic updater that works with any plugin.
|
||||
* Reads plugin headers and constructs update URL automatically.
|
||||
*/
|
||||
class Robotstxt_Updater {
|
||||
|
||||
/**
|
||||
* Plugin file path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $plugin_file_path;
|
||||
|
||||
/**
|
||||
* Plugin basename (e.g., 'my-plugin/my-plugin.php').
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $plugin_basename;
|
||||
|
||||
/**
|
||||
* Plugin slug (directory name).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $plugin_slug;
|
||||
|
||||
/**
|
||||
* Remote JSON URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $json_url;
|
||||
|
||||
/**
|
||||
* Cache key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $cache_key;
|
||||
|
||||
/**
|
||||
* Plugin headers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $plugin_data;
|
||||
|
||||
/**
|
||||
* Initialize the updater.
|
||||
*
|
||||
* Usage in your main plugin file:
|
||||
* require_once __DIR__ . '/robotstxt-updater.php';
|
||||
* Robotstxt_Updater::init( __FILE__ );
|
||||
*
|
||||
* @param string $plugin_file_path Absolute path to the main plugin file.
|
||||
*/
|
||||
public static function init( string $plugin_file_path ): void {
|
||||
$instance = new self( $plugin_file_path );
|
||||
$instance->register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $plugin_file_path Absolute path to the main plugin file.
|
||||
*/
|
||||
private function __construct( string $plugin_file_path ) {
|
||||
$this->plugin_file_path = $plugin_file_path;
|
||||
$this->plugin_basename = plugin_basename( $plugin_file_path );
|
||||
$this->plugin_slug = dirname( $this->plugin_basename );
|
||||
$this->plugin_data = $this->get_plugin_data();
|
||||
$this->json_url = $this->build_json_url();
|
||||
$this->cache_key = 'robotstxt_updater_' . md5( $this->plugin_basename );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register WordPress hooks.
|
||||
*/
|
||||
private function register(): void {
|
||||
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'inject_update_info' ) );
|
||||
add_filter( 'plugins_api', array( $this, 'provide_plugin_details' ), 10, 3 );
|
||||
add_action( 'admin_init', array( $this, 'handle_cache_clear' ) );
|
||||
add_action( 'robotstxt_updater_clear_cache', array( $this, 'clear_cache' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin headers.
|
||||
*
|
||||
* @return array Plugin data.
|
||||
*/
|
||||
private function get_plugin_data(): array {
|
||||
if ( ! function_exists( 'get_plugin_data' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
return get_plugin_data( $this->plugin_file_path, false, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build JSON URL from plugin headers.
|
||||
*
|
||||
* Tries to use "Gitea Plugin URI" header to construct the URL.
|
||||
* Falls back to Plugin URI if Gitea URI is not available.
|
||||
*
|
||||
* @return string JSON URL.
|
||||
*/
|
||||
private function build_json_url(): string {
|
||||
// Try Gitea Plugin URI (format: "OWNER/REPO" or full URL).
|
||||
if ( ! empty( $this->plugin_data['Gitea Plugin URI'] ) ) {
|
||||
$gitea_uri = $this->plugin_data['Gitea Plugin URI'];
|
||||
|
||||
// If it's already a full URL, use it.
|
||||
if ( str_starts_with( $gitea_uri, 'http' ) ) {
|
||||
// Extract base URL and construct JSON path.
|
||||
return rtrim( $gitea_uri, '/' ) . '/raw/branch/main/update.json';
|
||||
}
|
||||
|
||||
// If it's in format "OWNER/REPO", construct full URL.
|
||||
if ( preg_match( '#^[^/]+/[^/]+$#', $gitea_uri ) ) {
|
||||
return "https://git.robotstxt.es/{$gitea_uri}/raw/branch/main/update.json";
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try to extract from Plugin URI.
|
||||
if ( ! empty( $this->plugin_data['PluginURI'] ) ) {
|
||||
$plugin_uri = $this->plugin_data['PluginURI'];
|
||||
if ( str_contains( $plugin_uri, 'git.robotstxt.es' ) ) {
|
||||
return rtrim( $plugin_uri, '/' ) . '/raw/branch/main/update.json';
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: construct from plugin slug.
|
||||
return "https://git.robotstxt.es/ROBOTSTXT/{$this->plugin_slug}/raw/branch/main/update.json";
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject update info into WP's plugin update transient.
|
||||
*
|
||||
* @param object|mixed $transient The update_plugins transient.
|
||||
*
|
||||
* @return object The modified transient.
|
||||
*/
|
||||
public function inject_update_info( $transient ) {
|
||||
if ( ! is_object( $transient ) ) {
|
||||
$transient = new stdClass();
|
||||
}
|
||||
|
||||
if ( empty( $transient->checked ) || ! is_array( $transient->checked ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( empty( $transient->checked[ $this->plugin_basename ] ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
$current_version = $transient->checked[ $this->plugin_basename ];
|
||||
$remote = $this->get_remote_data();
|
||||
|
||||
if ( empty( $remote['version'] ) || empty( $remote['download_url'] ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( ! $this->is_compatible( $remote ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( version_compare( $remote['version'], $current_version, '>' ) ) {
|
||||
$update = (object) array(
|
||||
'slug' => $remote['slug'] ?? $this->plugin_slug,
|
||||
'plugin' => $this->plugin_basename,
|
||||
'new_version' => $remote['version'],
|
||||
'url' => $remote['homepage'] ?? $this->plugin_data['PluginURI'] ?? '',
|
||||
'package' => $remote['download_url'],
|
||||
'tested' => $remote['tested'] ?? '',
|
||||
'requires' => $remote['requires'] ?? '',
|
||||
'requires_php' => $remote['requires_php'] ?? '',
|
||||
);
|
||||
|
||||
$transient->response[ $this->plugin_basename ] = $update;
|
||||
}
|
||||
|
||||
return $transient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide "View details" modal content.
|
||||
*
|
||||
* @param false|object|array $result The result object or array.
|
||||
* @param string $action The type of information being requested.
|
||||
* @param object $args Plugin API arguments.
|
||||
*
|
||||
* @return false|object The plugin information object or false.
|
||||
*/
|
||||
public function provide_plugin_details( $result, string $action, object $args ) {
|
||||
if ( 'plugin_information' !== $action ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( empty( $args->slug ) || $args->slug !== $this->plugin_slug ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$remote = $this->get_remote_data();
|
||||
|
||||
if ( empty( $remote['version'] ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return (object) array(
|
||||
'name' => $remote['name'] ?? $this->plugin_data['Name'] ?? $this->plugin_slug,
|
||||
'slug' => $remote['slug'] ?? $this->plugin_slug,
|
||||
'version' => $remote['version'],
|
||||
'author' => $remote['author'] ?? $this->plugin_data['Author'] ?? '',
|
||||
'homepage' => $remote['homepage'] ?? $this->plugin_data['PluginURI'] ?? '',
|
||||
'requires' => $remote['requires'] ?? '',
|
||||
'tested' => $remote['tested'] ?? '',
|
||||
'requires_php' => $remote['requires_php'] ?? '',
|
||||
'sections' => array(
|
||||
'description' => $remote['description'] ?? $this->plugin_data['Description'] ?? '',
|
||||
'changelog' => $remote['changelog'] ?? '',
|
||||
),
|
||||
'download_link' => $remote['download_url'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote data with caching and HMAC signature verification.
|
||||
*
|
||||
* @return array Remote data.
|
||||
*/
|
||||
private function get_remote_data(): array {
|
||||
$cached = get_site_transient( $this->cache_key );
|
||||
|
||||
// Verify HMAC signature if AUTH_SALT is defined and cache has signature.
|
||||
if ( false !== $cached && defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) {
|
||||
if ( is_array( $cached ) && isset( $cached['signature'], $cached['data'] ) ) {
|
||||
$expected_sig = hash_hmac( 'sha256', $this->cache_key . serialize( $cached['data'] ), AUTH_SALT );
|
||||
|
||||
if ( hash_equals( $expected_sig, $cached['signature'] ) ) {
|
||||
// Signature valid, return data.
|
||||
return is_array( $cached['data'] ) ? $cached['data'] : array();
|
||||
}
|
||||
|
||||
// Signature invalid, delete corrupted cache.
|
||||
delete_site_transient( $this->cache_key );
|
||||
$cached = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid cache, fetch fresh data.
|
||||
if ( false === $cached ) {
|
||||
$remote = $this->fetch_json();
|
||||
|
||||
// Store with HMAC signature if AUTH_SALT is available.
|
||||
if ( defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) {
|
||||
$payload = array(
|
||||
'data' => $remote ?: array(),
|
||||
'timestamp' => time(),
|
||||
'signature' => hash_hmac( 'sha256', $this->cache_key . serialize( $remote ?: array() ), AUTH_SALT ),
|
||||
);
|
||||
set_site_transient( $this->cache_key, $payload, 6 * HOUR_IN_SECONDS );
|
||||
} else {
|
||||
// Fallback to standard caching.
|
||||
set_site_transient( $this->cache_key, $remote ?: array(), 6 * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return is_array( $remote ) ? $remote : array();
|
||||
}
|
||||
|
||||
// Legacy cache format without signature (backward compatibility).
|
||||
return is_array( $cached ) ? $cached : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch JSON from remote URL.
|
||||
*
|
||||
* @return array Decoded JSON data.
|
||||
*/
|
||||
private function fetch_json(): array {
|
||||
$response = wp_remote_get(
|
||||
$this->json_url,
|
||||
array(
|
||||
'timeout' => 10,
|
||||
'headers' => array(
|
||||
'Accept' => 'application/json',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$code = (int) wp_remote_retrieve_response_code( $response );
|
||||
if ( $code < 200 || $code >= 300 ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$data = json_decode( $body, true );
|
||||
|
||||
return is_array( $data ) ? $data : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check compatibility.
|
||||
*
|
||||
* @param array $remote Remote data.
|
||||
*
|
||||
* @return bool True if compatible.
|
||||
*/
|
||||
private function is_compatible( array $remote ): bool {
|
||||
if ( ! empty( $remote['requires_php'] ) ) {
|
||||
if ( version_compare( PHP_VERSION, $remote['requires_php'], '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $remote['requires'] ) ) {
|
||||
if ( version_compare( get_bloginfo( 'version' ), $remote['requires'], '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle manual cache clear via URL parameter.
|
||||
*/
|
||||
public function handle_cache_clear(): void {
|
||||
// Check if this is a cache clear request first.
|
||||
$clear_cache = filter_input( INPUT_GET, 'robotstxt_clear_update_cache', FILTER_UNSAFE_RAW );
|
||||
if ( null === $clear_cache ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a cache clear request - now verify nonce.
|
||||
$nonce_raw = filter_input( INPUT_GET, '_wpnonce', FILTER_UNSAFE_RAW );
|
||||
$nonce = $nonce_raw ? sanitize_text_field( wp_unslash( $nonce_raw ) ) : '';
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, 'robotstxt_clear_update_cache' ) ) {
|
||||
wp_die( esc_html__( 'Security check failed', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
// Check permissions.
|
||||
if ( ! current_user_can( 'update_plugins' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-ai-translator' ) );
|
||||
}
|
||||
|
||||
$this->clear_cache();
|
||||
wp_safe_redirect( remove_query_arg( array( 'robotstxt_clear_update_cache', '_wpnonce' ) ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear update cache.
|
||||
*/
|
||||
public function clear_cache(): void {
|
||||
delete_site_transient( $this->cache_key );
|
||||
delete_site_transient( 'update_plugins' );
|
||||
}
|
||||
}
|
||||
}
|
||||
16
update.json
16
update.json
|
|
@ -1,20 +1,20 @@
|
|||
{
|
||||
"name": "AI Translator (by ROBOTSTXT)",
|
||||
"slug": "robotstxt-ai-translator",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator/releases/download/1.0.0/robotstxt-ai-translator-1.0.0.zip",
|
||||
"version": "1.1.0",
|
||||
"download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator/releases/download/1.1.0/robotstxt-ai-translator-1.1.0.zip",
|
||||
"requires": "7.0",
|
||||
"requires_php": "7.4",
|
||||
"tested": "7.0",
|
||||
"last_updated": "2026-05-23",
|
||||
"tested": "7.1",
|
||||
"last_updated": "2026-05-25",
|
||||
"author": "ROBOTSTXT",
|
||||
"author_profile": "https://www.robotstxt.es/",
|
||||
"homepage": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator",
|
||||
"description": "Translate post titles and content from the WordPress editor using the native WordPress AI plugin as backend. Multisite-ready with global or per-site configuration.",
|
||||
"changelog": "<h3>1.0.0 - 2026-05-23</h3><ul><li><strong>Added:</strong> Block editor sidebar and Classic editor metabox with language selector and Translate button.</li><li><strong>Added:</strong> REST endpoint POST /wp-json/ai-translator/v1/translate.</li><li><strong>Added:</strong> WP-CLI command wp ai-translator translate for single-post and bulk translation.</li><li><strong>Added:</strong> Network admin settings page with global / per-site configuration mode.</li><li><strong>Added:</strong> Site admin settings page with title and content translation toggles.</li><li><strong>Added:</strong> Model recommendations table in the settings pages.</li></ul>",
|
||||
"description": "Translate post titles and content from the WordPress editor using the native WordPress AI client (available in WP 7.0+). Multisite-ready with global or per-site configuration.",
|
||||
"changelog": "<h3>1.1.0 - 2026-05-25</h3><ul><li><strong>Added:</strong> Excerpt translation — post_excerpt is now a translatable field with its own settings toggle.</li><li><strong>Added:</strong> MultilingualPress integration — auto-translate new connected posts at creation time (opt-in).</li><li><strong>Changed:</strong> Removed Requires Plugins: ai header — wp_ai_client_prompt() is native to WordPress 7.0; works with any configured AI provider.</li><li><strong>Changed:</strong> Admin notice now fires when no text-generation provider is configured.</li></ul><h3>1.0.0 - 2026-05-23</h3><ul><li><strong>Added:</strong> Block editor sidebar and Classic editor metabox with language selector and Translate button.</li><li><strong>Added:</strong> REST endpoint POST /wp-json/ai-translator/v1/translate.</li><li><strong>Added:</strong> WP-CLI command wp ai-translator translate for single-post and bulk translation.</li><li><strong>Added:</strong> Network admin settings page with global / per-site configuration mode.</li><li><strong>Added:</strong> Site admin settings page with title and content translation toggles.</li><li><strong>Added:</strong> Model recommendations table in the settings pages.</li></ul>",
|
||||
"sections": {
|
||||
"description": "Translate post titles and content from the WordPress editor using the native WordPress AI plugin as backend. Multisite-ready with global or per-site configuration.",
|
||||
"changelog": "<h3>1.0.0 - 2026-05-23</h3><ul><li><strong>Added:</strong> Block editor sidebar and Classic editor metabox with language selector and Translate button.</li><li><strong>Added:</strong> REST endpoint POST /wp-json/ai-translator/v1/translate.</li><li><strong>Added:</strong> WP-CLI command wp ai-translator translate for single-post and bulk translation.</li><li><strong>Added:</strong> Network admin settings page with global / per-site configuration mode.</li><li><strong>Added:</strong> Site admin settings page with title and content translation toggles.</li><li><strong>Added:</strong> Model recommendations table in the settings pages.</li></ul>"
|
||||
"description": "Translate post titles and content from the WordPress editor using the native WordPress AI client (available in WP 7.0+). Multisite-ready with global or per-site configuration.",
|
||||
"changelog": "<h3>1.1.0 - 2026-05-25</h3><ul><li><strong>Added:</strong> Excerpt translation — post_excerpt is now a translatable field with its own settings toggle.</li><li><strong>Added:</strong> MultilingualPress integration — auto-translate new connected posts at creation time (opt-in).</li><li><strong>Changed:</strong> Removed Requires Plugins: ai header — wp_ai_client_prompt() is native to WordPress 7.0; works with any configured AI provider.</li><li><strong>Changed:</strong> Admin notice now fires when no text-generation provider is configured.</li></ul><h3>1.0.0 - 2026-05-23</h3><ul><li><strong>Added:</strong> Block editor sidebar and Classic editor metabox with language selector and Translate button.</li><li><strong>Added:</strong> REST endpoint POST /wp-json/ai-translator/v1/translate.</li><li><strong>Added:</strong> WP-CLI command wp ai-translator translate for single-post and bulk translation.</li><li><strong>Added:</strong> Network admin settings page with global / per-site configuration mode.</li><li><strong>Added:</strong> Site admin settings page with title and content translation toggles.</li><li><strong>Added:</strong> Model recommendations table in the settings pages.</li></ul>"
|
||||
},
|
||||
"banners": {
|
||||
"low": "",
|
||||
|
|
|
|||
Loading…
Reference in a new issue