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.
|
* 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.
|
* @param {string} targetLocale WordPress locale code.
|
||||||
* @returns {Promise<Object>} Promise resolving to the translated values.
|
* @returns {Promise<Object>} Promise resolving to the translated values.
|
||||||
*/
|
*/
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveFields( wantTitle, wantContent ) {
|
function resolveFields( wantTitle, wantContent, wantExcerpt ) {
|
||||||
var fields = [];
|
var fields = [];
|
||||||
if ( wantTitle && data.settings.translateTitle ) {
|
if ( wantTitle && data.settings.translateTitle ) {
|
||||||
fields.push( 'title' );
|
fields.push( 'title' );
|
||||||
|
|
@ -58,6 +58,9 @@
|
||||||
if ( wantContent && data.settings.translateContent ) {
|
if ( wantContent && data.settings.translateContent ) {
|
||||||
fields.push( 'content' );
|
fields.push( 'content' );
|
||||||
}
|
}
|
||||||
|
if ( wantExcerpt && data.settings.translateExcerpt ) {
|
||||||
|
fields.push( 'excerpt' );
|
||||||
|
}
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +75,7 @@
|
||||||
* Applies a translation response using the right method for the current editor.
|
* 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 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 {Object} response Translation response.
|
||||||
* @param {Document} doc Document where the metabox lives (for classic fallback).
|
* @param {Document} doc Document where the metabox lives (for classic fallback).
|
||||||
|
|
@ -98,6 +101,9 @@
|
||||||
wp.data.dispatch( 'core/editor' ).editPost( { content: response.content } );
|
wp.data.dispatch( 'core/editor' ).editPost( { content: response.content } );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ( response.excerpt !== undefined ) {
|
||||||
|
wp.data.dispatch( 'core/editor' ).editPost( { excerpt: response.excerpt } );
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,6 +117,12 @@
|
||||||
if ( response.content !== undefined ) {
|
if ( response.content !== undefined ) {
|
||||||
applyContentToClassic( response.content, doc );
|
applyContentToClassic( response.content, doc );
|
||||||
}
|
}
|
||||||
|
if ( response.excerpt !== undefined ) {
|
||||||
|
var excerptTextarea = doc.getElementById( 'excerpt' );
|
||||||
|
if ( excerptTextarea ) {
|
||||||
|
excerptTextarea.value = response.excerpt;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyContentToClassic( content, doc ) {
|
function applyContentToClassic( content, doc ) {
|
||||||
|
|
@ -159,7 +171,11 @@
|
||||||
return;
|
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 ) {
|
if ( fields.length === 0 ) {
|
||||||
setStatus( labels.noFields || '', 'warning' );
|
setStatus( labels.noFields || '', 'warning' );
|
||||||
return;
|
return;
|
||||||
|
|
@ -244,6 +260,10 @@
|
||||||
var translateContent = contentState[0];
|
var translateContent = contentState[0];
|
||||||
var setTranslateContent = contentState[1];
|
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 coreEditor = dataStore.select( 'core/editor' );
|
||||||
var isDirty = coreEditor && typeof coreEditor.isEditedPostDirty === 'function' ? coreEditor.isEditedPostDirty() : false;
|
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' },
|
return el( 'div', { className: 'ai-translator-sidebar' },
|
||||||
el( components.Notice, { status: 'info', isDismissible: false }, labels.noFields || '' )
|
el( components.Notice, { status: 'info', isDismissible: false }, labels.noFields || '' )
|
||||||
);
|
);
|
||||||
|
|
@ -270,7 +290,7 @@
|
||||||
} );
|
} );
|
||||||
|
|
||||||
function runTranslate() {
|
function runTranslate() {
|
||||||
var fields = resolveFields( translateTitle, translateContent );
|
var fields = resolveFields( translateTitle, translateContent, translateExcerpt );
|
||||||
|
|
||||||
if ( fields.length === 0 ) {
|
if ( fields.length === 0 ) {
|
||||||
setNotice( { status: 'warning', message: labels.noFields || '' } );
|
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, {
|
children.push( el( components.Button, {
|
||||||
key: 'submit',
|
key: 'submit',
|
||||||
variant: 'primary',
|
variant: 'primary',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,39 @@
|
||||||
== Changelog ==
|
== 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 =
|
= 1.0.0 =
|
||||||
|
|
||||||
_Release date: 2026-05-23_
|
_Release date: 2026-05-23_
|
||||||
|
|
@ -21,12 +55,13 @@ _Release date: 2026-05-23_
|
||||||
|
|
||||||
**Compatibility**
|
**Compatibility**
|
||||||
|
|
||||||
* WordPress: 7.0
|
* WordPress: 7.0 - 7.1
|
||||||
* PHP: 7.4 - 8.5
|
* PHP: 7.4 - 8.5
|
||||||
* WP-CLI: 2.10 or newer
|
* WP-CLI: 2.12 or newer
|
||||||
|
|
||||||
**Tests**
|
**Tests**
|
||||||
|
|
||||||
* PHP Coding Standards: WordPress-Core, WordPress-Docs, WordPress-Extra
|
* PHP Coding Standards: WordPress-Core, WordPress-Docs, WordPress-Extra
|
||||||
* PHPCompatibility: 7.4 - 8.5
|
* PHPCompatibility: 7.4 - 8.5
|
||||||
* PHPStan: level 9
|
* 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">
|
<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'] ); ?> />
|
<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' ); ?>
|
<?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>
|
</label>
|
||||||
|
|
||||||
<?php if ( is_multisite() ) : ?>
|
<?php if ( is_multisite() ) : ?>
|
||||||
<p class="description">
|
<p class="description">
|
||||||
<?php
|
<?php
|
||||||
printf(
|
printf(
|
||||||
/* translators: 1: title default, 2: content default. */
|
/* translators: 1: title default, 2: content default, 3: excerpt default. */
|
||||||
esc_html__( 'Network defaults: title %1$s, content %2$s.', 'robotstxt-ai-translator' ),
|
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_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>
|
</p>
|
||||||
|
|
@ -238,6 +244,26 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
@ -315,6 +341,11 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
||||||
<label for="ai-translator-net-translate-content">
|
<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'] ); ?> />
|
<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' ); ?>
|
<?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>
|
</label>
|
||||||
|
|
||||||
<p class="description">
|
<p class="description">
|
||||||
|
|
@ -323,6 +354,26 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
@ -514,6 +565,11 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
||||||
/**
|
/**
|
||||||
* Sanitises a raw settings array into a strict boolean schema.
|
* 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
|
* @since 1.0.0
|
||||||
*
|
*
|
||||||
* @param array<string,mixed> $raw Raw input from the form.
|
* @param array<string,mixed> $raw Raw input from the form.
|
||||||
|
|
@ -524,6 +580,8 @@ if ( ! class_exists( 'AI_Translator_Admin' ) ) {
|
||||||
return array(
|
return array(
|
||||||
'translate_title' => ! empty( $raw['translate_title'] ),
|
'translate_title' => ! empty( $raw['translate_title'] ),
|
||||||
'translate_content' => ! empty( $raw['translate_content'] ),
|
'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' ) );
|
WP_CLI::error( __( 'The --post-id and --post-type flags are mutually exclusive.', 'robotstxt-ai-translator' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
$current_user = wp_get_current_user();
|
// Validate locale before AI checks so the user sees the right error when
|
||||||
if ( ! $current_user || 0 === (int) $current_user->ID ) {
|
// both the locale is missing and the AI provider is unconfigured.
|
||||||
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' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! in_array( $locale, $this->translator->get_installed_locales(), true ) ) {
|
if ( ! in_array( $locale, $this->translator->get_installed_locales(), true ) ) {
|
||||||
WP_CLI::error(
|
WP_CLI::error(
|
||||||
sprintf(
|
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();
|
$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' ) );
|
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;
|
$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 );
|
$result = wp_update_post( $update, true );
|
||||||
|
|
||||||
if ( is_wp_error( $result ) ) {
|
if ( is_wp_error( $result ) ) {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
* @var array<int,string>
|
* @var array<int,string>
|
||||||
*/
|
*/
|
||||||
const ALLOWED_FIELDS = array( 'title', 'content' );
|
const ALLOWED_FIELDS = array( 'title', 'content', 'excerpt' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings handler.
|
* Settings handler.
|
||||||
|
|
@ -210,6 +210,10 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
||||||
if ( in_array( 'content', $fields, true ) && ! empty( $site_settings['translate_content'] ) ) {
|
if ( in_array( 'content', $fields, true ) && ! empty( $site_settings['translate_content'] ) ) {
|
||||||
$allowed[] = '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 ) ) {
|
if ( empty( $allowed ) ) {
|
||||||
return new WP_Error(
|
return new WP_Error(
|
||||||
|
|
@ -237,6 +241,14 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
||||||
$response['content'] = $translated;
|
$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 );
|
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 ); ?>">
|
<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() ) : ?>
|
<?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>
|
<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'] ) ) : ?>
|
<?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>
|
<p><?php echo esc_html__( 'No translation fields are enabled in settings.', 'robotstxt-ai-translator' ); ?></p>
|
||||||
<?php elseif ( empty( $languages ) ) : ?>
|
<?php elseif ( empty( $languages ) ) : ?>
|
||||||
<p><?php echo esc_html__( 'No languages are installed on this site.', 'robotstxt-ai-translator' ); ?></p>
|
<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(
|
'settings' => array(
|
||||||
'translateTitle' => ! empty( $settings['translate_title'] ),
|
'translateTitle' => ! empty( $settings['translate_title'] ),
|
||||||
'translateContent' => ! empty( $settings['translate_content'] ),
|
'translateContent' => ! empty( $settings['translate_content'] ),
|
||||||
|
'translateExcerpt' => ! empty( $settings['translate_excerpt'] ),
|
||||||
),
|
),
|
||||||
'i18n' => array(
|
'i18n' => array(
|
||||||
'translate' => __( 'Translate', 'robotstxt-ai-translator' ),
|
'translate' => __( 'Translate', 'robotstxt-ai-translator' ),
|
||||||
|
|
@ -435,10 +448,11 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
||||||
'genericError' => __( 'Translation failed.', 'robotstxt-ai-translator' ),
|
'genericError' => __( 'Translation failed.', 'robotstxt-ai-translator' ),
|
||||||
'noLanguages' => __( 'No languages are installed on this site.', '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' ),
|
'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' ),
|
'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' ),
|
'translateTitle' => __( 'Translate title', 'robotstxt-ai-translator' ),
|
||||||
'translateContent' => __( 'Translate content', '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;
|
return (int) $post->ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||||
$post_get = wp_unslash( $_GET['post'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
if ( isset( $_GET['post'] ) && is_scalar( $_GET['post'] ) ) {
|
||||||
return is_scalar( $post_get ) ? absint( $post_get ) : 0;
|
return absint( (string) $_GET['post'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
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(
|
const DEFAULTS = array(
|
||||||
'translate_title' => true,
|
'translate_title' => true,
|
||||||
'translate_content' => 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 {
|
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
|
* @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() {
|
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() ) {
|
if ( ! $this->is_available() ) {
|
||||||
return new WP_Error(
|
return new WP_Error(
|
||||||
'ai_translator_unavailable',
|
'ai_translator_disabled',
|
||||||
__( 'The WordPress AI client is not available. Install and activate the WordPress AI plugin.', 'robotstxt-ai-translator' )
|
__( 'AI features are disabled for this WordPress installation.', 'robotstxt-ai-translator' )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! $this->is_supported() ) {
|
if ( ! $this->is_supported() ) {
|
||||||
return new WP_Error(
|
return new WP_Error(
|
||||||
'ai_translator_unsupported',
|
'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 ""
|
msgid ""
|
||||||
msgstr ""
|
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"
|
"Report-Msgid-Bugs-To: https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator\n"
|
||||||
"Last-Translator: ROBOTSTXT <hello@robotstxt.es>\n"
|
"Last-Translator: ROBOTSTXT <hello@robotstxt.es>\n"
|
||||||
"Language-Team: Catalan\n"
|
"Language-Team: Catalan\n"
|
||||||
"Language: ca\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"POT-Creation-Date: 2026-05-23T10:07:42+00:00\n"
|
"POT-Creation-Date: 2026-05-23T10:07:42+00:00\n"
|
||||||
"PO-Revision-Date: 2026-05-23 10:30+0000\n"
|
"PO-Revision-Date: 2026-05-23 10:30+0000\n"
|
||||||
|
"Language: ca\n"
|
||||||
"X-Generator: hand-written\n"
|
"X-Generator: hand-written\n"
|
||||||
"X-Domain: robotstxt-ai-translator\n"
|
"X-Domain: robotstxt-ai-translator\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
#. Plugin Name of the plugin
|
#. Plugin Name of the plugin
|
||||||
|
#: robotstxt-ai-translator.php
|
||||||
msgid "AI Translator (by ROBOTSTXT)"
|
msgid "AI Translator (by ROBOTSTXT)"
|
||||||
msgstr "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
|
#. Author of the plugin
|
||||||
|
#: robotstxt-ai-translator.php
|
||||||
msgid "ROBOTSTXT"
|
msgid "ROBOTSTXT"
|
||||||
msgstr "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"
|
msgid "AI Translator"
|
||||||
msgstr "AI Translator"
|
msgstr "AI Translator"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:153
|
||||||
|
#: includes/class-ai-translator-admin.php:171
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "Ajustaments"
|
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."
|
msgid "You do not have permission to access this page."
|
||||||
msgstr "No tens permís per accedir a aquesta pàgina."
|
msgstr "No tens permís per accedir a aquesta pàgina."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:196
|
||||||
msgid "AI Translator Settings"
|
msgid "AI Translator Settings"
|
||||||
msgstr "Ajustaments d'AI Translator"
|
msgstr "Ajustaments d'AI Translator"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:200
|
||||||
msgid "Settings saved."
|
msgid "Settings saved."
|
||||||
msgstr "Ajustaments desats."
|
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"
|
msgid "Translation fields"
|
||||||
msgstr "Camps a traduir"
|
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"
|
msgid "Translate title"
|
||||||
msgstr "Tradueix el títol"
|
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"
|
msgid "Translate content"
|
||||||
msgstr "Tradueix el contingut"
|
msgstr "Tradueix el contingut"
|
||||||
|
|
||||||
#. translators: 1: title default, 2: content default.
|
#: includes/class-ai-translator-admin.php:237
|
||||||
#, php-format
|
#: includes/class-ai-translator-admin.php:238
|
||||||
msgid "Network defaults: title %1$s, content %2$s."
|
#: includes/class-ai-translator-admin.php:239
|
||||||
msgstr "Valors predeterminats de la xarxa: títol %1$s, contingut %2$s."
|
|
||||||
|
|
||||||
msgid "enabled"
|
msgid "enabled"
|
||||||
msgstr "activat"
|
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"
|
msgid "disabled"
|
||||||
msgstr "desactivat"
|
msgstr "desactivat"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:296
|
||||||
msgid "AI Translator Network Settings"
|
msgid "AI Translator Network Settings"
|
||||||
msgstr "Ajustaments de xarxa d'AI Translator"
|
msgstr "Ajustaments de xarxa d'AI Translator"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:300
|
||||||
msgid "Network settings saved."
|
msgid "Network settings saved."
|
||||||
msgstr "Ajustaments de xarxa desats."
|
msgstr "Ajustaments de xarxa desats."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:310
|
||||||
|
#: includes/class-ai-translator-admin.php:313
|
||||||
msgid "Configuration mode"
|
msgid "Configuration mode"
|
||||||
msgstr "Mode de configuració"
|
msgstr "Mode de configuració"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:317
|
||||||
msgid "Global configuration: a single setting applies to every site."
|
msgid "Global configuration: a single setting applies to every site."
|
||||||
msgstr "Configuració global: un sol ajustament s'aplica a tots els llocs."
|
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."
|
msgid "Per-site configuration: each site can override these defaults."
|
||||||
msgstr "Configuració per lloc: cada lloc pot sobreescriure aquests valors predeterminats."
|
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."
|
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."
|
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."
|
msgid "You do not have permission to perform this action."
|
||||||
msgstr "No tens permís per dur a terme aquesta acció."
|
msgstr "No tens permís per dur a terme aquesta acció."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:473
|
||||||
msgid "Recommended models"
|
msgid "Recommended models"
|
||||||
msgstr "Models recomanats"
|
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."
|
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ó."
|
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"
|
msgid "Use case"
|
||||||
msgstr "Cas d'ús"
|
msgstr "Cas d'ús"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:483
|
||||||
msgid "Recommended model"
|
msgid "Recommended model"
|
||||||
msgstr "Model recomanat"
|
msgstr "Model recomanat"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:484
|
||||||
msgid "Why"
|
msgid "Why"
|
||||||
msgstr "Per què"
|
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."
|
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."
|
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)"
|
msgid "European languages (ES, CA, FR, DE, IT, PT)"
|
||||||
msgstr "Llengües europees (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"
|
msgid "DeepL API Pro"
|
||||||
msgstr "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."
|
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."
|
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)"
|
msgid "Asian languages (ZH, JA, KO)"
|
||||||
msgstr "Llengües asiàtiques (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"
|
msgid "GPT-4o / GPT-5 or Claude Sonnet 4"
|
||||||
msgstr "GPT-4o / GPT-5 o 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."
|
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í."
|
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"
|
msgid "Marketing / brand tone"
|
||||||
msgstr "Màrqueting / to de marca"
|
msgstr "Màrqueting / to de marca"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:525
|
||||||
msgid "Claude Sonnet 4 / Opus 4"
|
msgid "Claude Sonnet 4 / Opus 4"
|
||||||
msgstr "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."
|
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."
|
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"
|
msgid "Technical documentation / code"
|
||||||
msgstr "Documentació tècnica / codi"
|
msgstr "Documentació tècnica / codi"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:530
|
||||||
msgid "GPT-4o / GPT-5"
|
msgid "GPT-4o / GPT-5"
|
||||||
msgstr "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."
|
msgid "Higher accuracy with variables, structured formats, and technical terminology."
|
||||||
msgstr "Major precisió amb variables, formats estructurats i terminologia tècnica."
|
msgstr "Major precisió amb variables, formats estructurats i terminologia tècnica."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:534
|
||||||
msgid "Long documents (100+ pages)"
|
msgid "Long documents (100+ pages)"
|
||||||
msgstr "Documents llargs (més de 100 pàgines)"
|
msgstr "Documents llargs (més de 100 pàgines)"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:535
|
||||||
msgid "Gemini 2.5 Pro"
|
msgid "Gemini 2.5 Pro"
|
||||||
msgstr "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."
|
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."
|
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"
|
msgid "High volume / low cost"
|
||||||
msgstr "Alt volum / baix cost"
|
msgstr "Alt volum / baix cost"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:540
|
||||||
msgid "DeepSeek-V3"
|
msgid "DeepSeek-V3"
|
||||||
msgstr "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)."
|
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)."
|
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"
|
msgid "Rare / indigenous languages"
|
||||||
msgstr "Llengües rares / indígenes"
|
msgstr "Llengües rares / indígenes"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:545
|
||||||
msgid "Claude Sonnet or Taskade Translate (multi-model routing)"
|
msgid "Claude Sonnet or Taskade Translate (multi-model routing)"
|
||||||
msgstr "Claude Sonnet o Taskade Translate (encaminament multimodel)"
|
msgstr "Claude Sonnet o Taskade Translate (encaminament multimodel)"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:546
|
||||||
msgid "Better coverage for uncommon language pairs."
|
msgid "Better coverage for uncommon language pairs."
|
||||||
msgstr "Millor cobertura en parells de llengües poc habituals."
|
msgstr "Millor cobertura en parells de llengües poc habituals."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:549
|
||||||
msgid "Maximum coverage (133+ languages)"
|
msgid "Maximum coverage (133+ languages)"
|
||||||
msgstr "Cobertura màxima (més de 133 llengües)"
|
msgstr "Cobertura màxima (més de 133 llengües)"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:550
|
||||||
msgid "Google Cloud Translation"
|
msgid "Google Cloud Translation"
|
||||||
msgstr "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."
|
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."
|
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."
|
msgid "The --locale flag is required."
|
||||||
msgstr "El paràmetre --locale és obligatori."
|
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."
|
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."
|
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."
|
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."
|
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.
|
#. translators: %s: locale code.
|
||||||
|
#: includes/class-ai-translator-cli.php:133
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "The locale %s is not installed on this site."
|
msgid "The locale %s is not installed on this site."
|
||||||
msgstr "La llengua %s no està instal·lada en aquest lloc."
|
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."
|
msgid "No translation fields are enabled in the plugin settings."
|
||||||
msgstr "No hi ha cap camp de traducció activat als ajustaments del connector."
|
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."
|
msgid "No posts matched the given criteria."
|
||||||
msgstr "Cap entrada coincideix amb els criteris donats."
|
msgstr "Cap entrada coincideix amb els criteris donats."
|
||||||
|
|
||||||
#. translators: 1: success count, 2: failure count, 3: locale.
|
#. translators: 1: success count, 2: failure count, 3: locale.
|
||||||
|
#: includes/class-ai-translator-cli.php:242
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "%1$d translated, %2$d failed (locale: %3$s)."
|
msgid "%1$d translated, %2$d failed (locale: %3$s)."
|
||||||
msgstr "%1$d traduïdes, %2$d amb errors (llengua: %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."
|
msgid "Sorry, you are not allowed to edit this post."
|
||||||
msgstr "Ho sentim, no tens permís per editar aquesta entrada."
|
msgstr "Ho sentim, no tens permís per editar aquesta entrada."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:190
|
||||||
msgid "Post not found."
|
msgid "Post not found."
|
||||||
msgstr "Entrada no trobada."
|
msgstr "Entrada no trobada."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:199
|
||||||
msgid "The selected language is not installed on this site."
|
msgid "The selected language is not installed on this site."
|
||||||
msgstr "La llengua seleccionada no està instal·lada en aquest lloc."
|
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."
|
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."
|
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."
|
#: includes/class-ai-translator-editor-ui.php:321
|
||||||
msgstr "El connector WordPress AI no està actiu. La traducció no està disponible."
|
#: includes/class-ai-translator-editor-ui.php:450
|
||||||
|
|
||||||
msgid "No translation fields are enabled in settings."
|
msgid "No translation fields are enabled in settings."
|
||||||
msgstr "No hi ha cap camp de traducció activat als ajustaments."
|
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."
|
msgid "No languages are installed on this site."
|
||||||
msgstr "No hi ha cap llengua instal·lada en aquest lloc."
|
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"
|
msgid "Target language"
|
||||||
msgstr "Llengua de destinació"
|
msgstr "Llengua de destinació"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:336
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:443
|
||||||
msgid "Translate"
|
msgid "Translate"
|
||||||
msgstr "Tradueix"
|
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."
|
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."
|
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…"
|
msgid "Translating…"
|
||||||
msgstr "Traduint…"
|
msgstr "Traduint…"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:447
|
||||||
msgid "Translation applied. Review and save the post."
|
msgid "Translation applied. Review and save the post."
|
||||||
msgstr "Traducció aplicada. Revisa i desa l'entrada."
|
msgstr "Traducció aplicada. Revisa i desa l'entrada."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:448
|
||||||
msgid "Translation failed."
|
msgid "Translation failed."
|
||||||
msgstr "La traducció ha fallat."
|
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."
|
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."
|
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."
|
#: includes/class-ai-translator-translator.php:102
|
||||||
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."
|
|
||||||
|
|
||||||
msgid "The target language is not installed on this site."
|
msgid "The target language is not installed on this site."
|
||||||
msgstr "La llengua de destinació no està instal·lada en aquest lloc."
|
msgstr "La llengua de destinació no està instal·lada en aquest lloc."
|
||||||
|
|
||||||
#. translators: %s: target language name.
|
#. translators: %s: target language name.
|
||||||
|
#: includes/class-ai-translator-translator.php:108
|
||||||
#, php-format
|
#, 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."
|
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."
|
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."
|
msgid "The AI service returned an unexpected response."
|
||||||
msgstr "El servei d'IA ha retornat una resposta inesperada."
|
msgstr "El servei d'IA ha retornat una resposta inesperada."
|
||||||
|
|
||||||
#. translators: %s: WordPress AI plugin link.
|
#: class-robotstxt-updater.php:456
|
||||||
#, 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"
|
|
||||||
|
|
||||||
msgid "You do not have sufficient permissions to access this page."
|
msgid "You do not have sufficient permissions to access this page."
|
||||||
msgstr "No tens permisos suficients per accedir a aquesta pàgina."
|
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 ""
|
msgid ""
|
||||||
msgstr ""
|
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"
|
"Report-Msgid-Bugs-To: https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator\n"
|
||||||
"Last-Translator: ROBOTSTXT <hello@robotstxt.es>\n"
|
"Last-Translator: ROBOTSTXT <hello@robotstxt.es>\n"
|
||||||
"Language-Team: Spanish (Spain)\n"
|
"Language-Team: Spanish (Spain)\n"
|
||||||
"Language: es_ES\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"POT-Creation-Date: 2026-05-23T10:07:42+00:00\n"
|
"POT-Creation-Date: 2026-05-23T10:07:42+00:00\n"
|
||||||
"PO-Revision-Date: 2026-05-23 10:30+0000\n"
|
"PO-Revision-Date: 2026-05-23 10:30+0000\n"
|
||||||
|
"Language: es_ES\n"
|
||||||
"X-Generator: hand-written\n"
|
"X-Generator: hand-written\n"
|
||||||
"X-Domain: robotstxt-ai-translator\n"
|
"X-Domain: robotstxt-ai-translator\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
#. Plugin Name of the plugin
|
#. Plugin Name of the plugin
|
||||||
|
#: robotstxt-ai-translator.php
|
||||||
msgid "AI Translator (by ROBOTSTXT)"
|
msgid "AI Translator (by ROBOTSTXT)"
|
||||||
msgstr "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
|
#. Author of the plugin
|
||||||
|
#: robotstxt-ai-translator.php
|
||||||
msgid "ROBOTSTXT"
|
msgid "ROBOTSTXT"
|
||||||
msgstr "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"
|
msgid "AI Translator"
|
||||||
msgstr "AI Translator"
|
msgstr "AI Translator"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:153
|
||||||
|
#: includes/class-ai-translator-admin.php:171
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "Ajustes"
|
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."
|
msgid "You do not have permission to access this page."
|
||||||
msgstr "No tienes permiso para acceder a esta página."
|
msgstr "No tienes permiso para acceder a esta página."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:196
|
||||||
msgid "AI Translator Settings"
|
msgid "AI Translator Settings"
|
||||||
msgstr "Ajustes de AI Translator"
|
msgstr "Ajustes de AI Translator"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:200
|
||||||
msgid "Settings saved."
|
msgid "Settings saved."
|
||||||
msgstr "Ajustes guardados."
|
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"
|
msgid "Translation fields"
|
||||||
msgstr "Campos a traducir"
|
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"
|
msgid "Translate title"
|
||||||
msgstr "Traducir título"
|
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"
|
msgid "Translate content"
|
||||||
msgstr "Traducir contenido"
|
msgstr "Traducir contenido"
|
||||||
|
|
||||||
#. translators: 1: title default, 2: content default.
|
#: includes/class-ai-translator-admin.php:237
|
||||||
#, php-format
|
#: includes/class-ai-translator-admin.php:238
|
||||||
msgid "Network defaults: title %1$s, content %2$s."
|
#: includes/class-ai-translator-admin.php:239
|
||||||
msgstr "Valores por defecto de la red: título %1$s, contenido %2$s."
|
|
||||||
|
|
||||||
msgid "enabled"
|
msgid "enabled"
|
||||||
msgstr "activado"
|
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"
|
msgid "disabled"
|
||||||
msgstr "desactivado"
|
msgstr "desactivado"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:296
|
||||||
msgid "AI Translator Network Settings"
|
msgid "AI Translator Network Settings"
|
||||||
msgstr "Ajustes de red de AI Translator"
|
msgstr "Ajustes de red de AI Translator"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:300
|
||||||
msgid "Network settings saved."
|
msgid "Network settings saved."
|
||||||
msgstr "Ajustes de red guardados."
|
msgstr "Ajustes de red guardados."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:310
|
||||||
|
#: includes/class-ai-translator-admin.php:313
|
||||||
msgid "Configuration mode"
|
msgid "Configuration mode"
|
||||||
msgstr "Modo de configuración"
|
msgstr "Modo de configuración"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:317
|
||||||
msgid "Global configuration: a single setting applies to every site."
|
msgid "Global configuration: a single setting applies to every site."
|
||||||
msgstr "Configuración global: un único ajuste se aplica a todos los sitios."
|
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."
|
msgid "Per-site configuration: each site can override these defaults."
|
||||||
msgstr "Configuración por sitio: cada sitio puede sobrescribir estos valores por defecto."
|
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."
|
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."
|
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."
|
msgid "You do not have permission to perform this action."
|
||||||
msgstr "No tienes permiso para realizar esta acción."
|
msgstr "No tienes permiso para realizar esta acción."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:473
|
||||||
msgid "Recommended models"
|
msgid "Recommended models"
|
||||||
msgstr "Modelos recomendados"
|
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."
|
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."
|
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"
|
msgid "Use case"
|
||||||
msgstr "Caso de uso"
|
msgstr "Caso de uso"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:483
|
||||||
msgid "Recommended model"
|
msgid "Recommended model"
|
||||||
msgstr "Modelo recomendado"
|
msgstr "Modelo recomendado"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:484
|
||||||
msgid "Why"
|
msgid "Why"
|
||||||
msgstr "Por qué"
|
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."
|
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."
|
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)"
|
msgid "European languages (ES, CA, FR, DE, IT, PT)"
|
||||||
msgstr "Idiomas europeos (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"
|
msgid "DeepL API Pro"
|
||||||
msgstr "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."
|
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."
|
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)"
|
msgid "Asian languages (ZH, JA, KO)"
|
||||||
msgstr "Idiomas asiáticos (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"
|
msgid "GPT-4o / GPT-5 or Claude Sonnet 4"
|
||||||
msgstr "GPT-4o / GPT-5 o 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."
|
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í."
|
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"
|
msgid "Marketing / brand tone"
|
||||||
msgstr "Marketing / tono de marca"
|
msgstr "Marketing / tono de marca"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:525
|
||||||
msgid "Claude Sonnet 4 / Opus 4"
|
msgid "Claude Sonnet 4 / Opus 4"
|
||||||
msgstr "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."
|
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."
|
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"
|
msgid "Technical documentation / code"
|
||||||
msgstr "Documentación técnica / código"
|
msgstr "Documentación técnica / código"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:530
|
||||||
msgid "GPT-4o / GPT-5"
|
msgid "GPT-4o / GPT-5"
|
||||||
msgstr "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."
|
msgid "Higher accuracy with variables, structured formats, and technical terminology."
|
||||||
msgstr "Mayor precisión con variables, formatos estructurados y terminología técnica."
|
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)"
|
msgid "Long documents (100+ pages)"
|
||||||
msgstr "Documentos largos (más de 100 páginas)"
|
msgstr "Documentos largos (más de 100 páginas)"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:535
|
||||||
msgid "Gemini 2.5 Pro"
|
msgid "Gemini 2.5 Pro"
|
||||||
msgstr "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."
|
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."
|
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"
|
msgid "High volume / low cost"
|
||||||
msgstr "Alto volumen / bajo coste"
|
msgstr "Alto volumen / bajo coste"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:540
|
||||||
msgid "DeepSeek-V3"
|
msgid "DeepSeek-V3"
|
||||||
msgstr "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)."
|
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)."
|
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"
|
msgid "Rare / indigenous languages"
|
||||||
msgstr "Idiomas raros / indígenas"
|
msgstr "Idiomas raros / indígenas"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:545
|
||||||
msgid "Claude Sonnet or Taskade Translate (multi-model routing)"
|
msgid "Claude Sonnet or Taskade Translate (multi-model routing)"
|
||||||
msgstr "Claude Sonnet o Taskade Translate (enrutado multimodelo)"
|
msgstr "Claude Sonnet o Taskade Translate (enrutado multimodelo)"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:546
|
||||||
msgid "Better coverage for uncommon language pairs."
|
msgid "Better coverage for uncommon language pairs."
|
||||||
msgstr "Mejor cobertura en pares de idiomas poco comunes."
|
msgstr "Mejor cobertura en pares de idiomas poco comunes."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:549
|
||||||
msgid "Maximum coverage (133+ languages)"
|
msgid "Maximum coverage (133+ languages)"
|
||||||
msgstr "Cobertura máxima (más de 133 idiomas)"
|
msgstr "Cobertura máxima (más de 133 idiomas)"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-admin.php:550
|
||||||
msgid "Google Cloud Translation"
|
msgid "Google Cloud Translation"
|
||||||
msgstr "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."
|
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."
|
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."
|
msgid "The --locale flag is required."
|
||||||
msgstr "El parámetro --locale es obligatorio."
|
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."
|
msgid "The --post-id and --post-type flags are mutually exclusive."
|
||||||
msgstr "Los parámetros --post-id y --post-type son mutuamente excluyentes."
|
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."
|
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."
|
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.
|
#. translators: %s: locale code.
|
||||||
|
#: includes/class-ai-translator-cli.php:133
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "The locale %s is not installed on this site."
|
msgid "The locale %s is not installed on this site."
|
||||||
msgstr "El idioma %s no está instalado en este sitio."
|
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."
|
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."
|
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."
|
msgid "No posts matched the given criteria."
|
||||||
msgstr "Ninguna entrada coincide con los criterios indicados."
|
msgstr "Ninguna entrada coincide con los criterios indicados."
|
||||||
|
|
||||||
#. translators: 1: success count, 2: failure count, 3: locale.
|
#. translators: 1: success count, 2: failure count, 3: locale.
|
||||||
|
#: includes/class-ai-translator-cli.php:242
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "%1$d translated, %2$d failed (locale: %3$s)."
|
msgid "%1$d translated, %2$d failed (locale: %3$s)."
|
||||||
msgstr "%1$d traducidas, %2$d con fallos (idioma: %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."
|
msgid "Sorry, you are not allowed to edit this post."
|
||||||
msgstr "Lo sentimos, no tienes permiso para editar esta entrada."
|
msgstr "Lo sentimos, no tienes permiso para editar esta entrada."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:190
|
||||||
msgid "Post not found."
|
msgid "Post not found."
|
||||||
msgstr "Entrada no encontrada."
|
msgstr "Entrada no encontrada."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:199
|
||||||
msgid "The selected language is not installed on this site."
|
msgid "The selected language is not installed on this site."
|
||||||
msgstr "El idioma seleccionado no está instalado en este sitio."
|
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."
|
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."
|
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."
|
#: includes/class-ai-translator-editor-ui.php:321
|
||||||
msgstr "El plugin WordPress AI no está activo. La traducción no está disponible."
|
#: includes/class-ai-translator-editor-ui.php:450
|
||||||
|
|
||||||
msgid "No translation fields are enabled in settings."
|
msgid "No translation fields are enabled in settings."
|
||||||
msgstr "No hay ningún campo de traducción activado en los ajustes."
|
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."
|
msgid "No languages are installed on this site."
|
||||||
msgstr "No hay ningún idioma instalado en este sitio."
|
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"
|
msgid "Target language"
|
||||||
msgstr "Idioma de destino"
|
msgstr "Idioma de destino"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:336
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:443
|
||||||
msgid "Translate"
|
msgid "Translate"
|
||||||
msgstr "Traducir"
|
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."
|
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."
|
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…"
|
msgid "Translating…"
|
||||||
msgstr "Traduciendo…"
|
msgstr "Traduciendo…"
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:447
|
||||||
msgid "Translation applied. Review and save the post."
|
msgid "Translation applied. Review and save the post."
|
||||||
msgstr "Traducción aplicada. Revisa y guarda la entrada."
|
msgstr "Traducción aplicada. Revisa y guarda la entrada."
|
||||||
|
|
||||||
|
#: includes/class-ai-translator-editor-ui.php:448
|
||||||
msgid "Translation failed."
|
msgid "Translation failed."
|
||||||
msgstr "La traducción ha fallado."
|
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."
|
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."
|
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."
|
#: includes/class-ai-translator-translator.php:102
|
||||||
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."
|
|
||||||
|
|
||||||
msgid "The target language is not installed on this site."
|
msgid "The target language is not installed on this site."
|
||||||
msgstr "El idioma de destino no está instalado en este sitio."
|
msgstr "El idioma de destino no está instalado en este sitio."
|
||||||
|
|
||||||
#. translators: %s: target language name.
|
#. translators: %s: target language name.
|
||||||
|
#: includes/class-ai-translator-translator.php:108
|
||||||
#, php-format
|
#, 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."
|
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."
|
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."
|
msgid "The AI service returned an unexpected response."
|
||||||
msgstr "El servicio de IA ha devuelto una respuesta inesperada."
|
msgstr "El servicio de IA ha devuelto una respuesta inesperada."
|
||||||
|
|
||||||
#. translators: %s: WordPress AI plugin link.
|
#: class-robotstxt-updater.php:456
|
||||||
#, 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"
|
|
||||||
|
|
||||||
msgid "You do not have sufficient permissions to access this page."
|
msgid "You do not have sufficient permissions to access this page."
|
||||||
msgstr "No tienes permisos suficientes para acceder a esta página."
|
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.
|
# This file is distributed under the GPL-3.0-or-later.
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
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"
|
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-ai-translator\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\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"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"X-Generator: WP-CLI 2.12.0\n"
|
"X-Generator: WP-CLI 2.12.0\n"
|
||||||
"X-Domain: robotstxt-ai-translator\n"
|
"X-Domain: robotstxt-ai-translator\n"
|
||||||
|
|
@ -26,7 +26,7 @@ msgstr ""
|
||||||
|
|
||||||
#. Description of the plugin
|
#. Description of the plugin
|
||||||
#: robotstxt-ai-translator.php
|
#: 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#. Author of the plugin
|
#. Author of the plugin
|
||||||
|
|
@ -39,12 +39,20 @@ msgstr ""
|
||||||
msgid "https://www.robotstxt.es/"
|
msgid "https://www.robotstxt.es/"
|
||||||
msgstr ""
|
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:112
|
||||||
#: includes/class-ai-translator-admin.php:113
|
#: includes/class-ai-translator-admin.php:113
|
||||||
#: includes/class-ai-translator-admin.php:130
|
#: includes/class-ai-translator-admin.php:130
|
||||||
#: includes/class-ai-translator-admin.php:131
|
#: includes/class-ai-translator-admin.php:131
|
||||||
#: includes/class-ai-translator-editor-ui.php:282
|
#: includes/class-ai-translator-editor-ui.php:294
|
||||||
#: includes/class-ai-translator-editor-ui.php:433
|
#: includes/class-ai-translator-editor-ui.php:446
|
||||||
msgid "AI Translator"
|
msgid "AI Translator"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -54,7 +62,7 @@ msgid "Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:187
|
#: 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."
|
msgid "You do not have permission to access this page."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -68,186 +76,211 @@ msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:211
|
#: includes/class-ai-translator-admin.php:211
|
||||||
#: includes/class-ai-translator-admin.php:214
|
#: includes/class-ai-translator-admin.php:214
|
||||||
#: includes/class-ai-translator-admin.php:304
|
#: includes/class-ai-translator-admin.php:330
|
||||||
#: includes/class-ai-translator-admin.php:308
|
#: includes/class-ai-translator-admin.php:334
|
||||||
msgid "Translation fields"
|
msgid "Translation fields"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:218
|
#: includes/class-ai-translator-admin.php:218
|
||||||
#: includes/class-ai-translator-admin.php:312
|
#: includes/class-ai-translator-admin.php:338
|
||||||
#: includes/class-ai-translator-editor-ui.php:440
|
#: includes/class-ai-translator-editor-ui.php:453
|
||||||
msgid "Translate title"
|
msgid "Translate title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:223
|
#: includes/class-ai-translator-admin.php:223
|
||||||
#: includes/class-ai-translator-admin.php:317
|
#: includes/class-ai-translator-admin.php:343
|
||||||
#: includes/class-ai-translator-editor-ui.php:441
|
#: includes/class-ai-translator-editor-ui.php:454
|
||||||
msgid "Translate content"
|
msgid "Translate content"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. translators: 1: title default, 2: content default.
|
#: includes/class-ai-translator-admin.php:228
|
||||||
#: includes/class-ai-translator-admin.php:231
|
#: includes/class-ai-translator-admin.php:348
|
||||||
#, php-format
|
#: includes/class-ai-translator-editor-ui.php:455
|
||||||
msgid "Network defaults: title %1$s, content %2$s."
|
msgid "Translate excerpt"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:232
|
#. translators: 1: title default, 2: content default, 3: excerpt default.
|
||||||
#: includes/class-ai-translator-admin.php:233
|
#: 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"
|
msgid "enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:232
|
#: includes/class-ai-translator-admin.php:237
|
||||||
#: includes/class-ai-translator-admin.php:233
|
#: includes/class-ai-translator-admin.php:238
|
||||||
|
#: includes/class-ai-translator-admin.php:239
|
||||||
msgid "disabled"
|
msgid "disabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:270
|
#: includes/class-ai-translator-admin.php:250
|
||||||
msgid "AI Translator Network Settings"
|
#: includes/class-ai-translator-admin.php:253
|
||||||
|
#: includes/class-ai-translator-admin.php:360
|
||||||
|
#: includes/class-ai-translator-admin.php:363
|
||||||
|
msgid "MultilingualPress"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:274
|
#: includes/class-ai-translator-admin.php:257
|
||||||
msgid "Network settings saved."
|
#: includes/class-ai-translator-admin.php:367
|
||||||
|
msgid "Auto-translate new connected posts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:284
|
#: includes/class-ai-translator-admin.php:261
|
||||||
#: includes/class-ai-translator-admin.php:287
|
#: includes/class-ai-translator-admin.php:371
|
||||||
msgid "Configuration mode"
|
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:291
|
|
||||||
msgid "Global configuration: a single setting applies to every site."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:296
|
#: 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."
|
msgid "Per-site configuration: each site can override these defaults."
|
||||||
msgstr ""
|
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."
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:346
|
#: includes/class-ai-translator-admin.php:397
|
||||||
#: includes/class-ai-translator-admin.php:378
|
#: includes/class-ai-translator-admin.php:429
|
||||||
msgid "You do not have permission to perform this action."
|
msgid "You do not have permission to perform this action."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:422
|
#: includes/class-ai-translator-admin.php:473
|
||||||
msgid "Recommended models"
|
msgid "Recommended models"
|
||||||
msgstr ""
|
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."
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:431
|
#: includes/class-ai-translator-admin.php:482
|
||||||
msgid "Use case"
|
msgid "Use case"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:432
|
#: includes/class-ai-translator-admin.php:483
|
||||||
msgid "Recommended model"
|
msgid "Recommended model"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:433
|
#: includes/class-ai-translator-admin.php:484
|
||||||
msgid "Why"
|
msgid "Why"
|
||||||
msgstr ""
|
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."
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:463
|
#: includes/class-ai-translator-admin.php:514
|
||||||
msgid "European languages (ES, CA, FR, DE, IT, PT)"
|
msgid "European languages (ES, CA, FR, DE, IT, PT)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:464
|
#: includes/class-ai-translator-admin.php:515
|
||||||
msgid "DeepL API Pro"
|
msgid "DeepL API Pro"
|
||||||
msgstr ""
|
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."
|
msgid "Best fluency and naturalness (92/100 in benchmarks). Formality control and custom glossaries."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:468
|
#: includes/class-ai-translator-admin.php:519
|
||||||
msgid "Asian languages (ZH, JA, KO)"
|
msgid "Asian languages (ZH, JA, KO)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:469
|
#: includes/class-ai-translator-admin.php:520
|
||||||
msgid "GPT-4o / GPT-5 or Claude Sonnet 4"
|
msgid "GPT-4o / GPT-5 or Claude Sonnet 4"
|
||||||
msgstr ""
|
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."
|
msgid "Better handling of implicit subjects, honorifics, and cultural references. DeepL falls behind here."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:473
|
#: includes/class-ai-translator-admin.php:524
|
||||||
msgid "Marketing / brand tone"
|
msgid "Marketing / brand tone"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:474
|
#: includes/class-ai-translator-admin.php:525
|
||||||
msgid "Claude Sonnet 4 / Opus 4"
|
msgid "Claude Sonnet 4 / Opus 4"
|
||||||
msgstr ""
|
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."
|
msgid "Better preservation of tone, brand voice, and nuance. Ideal for creative copy."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:478
|
#: includes/class-ai-translator-admin.php:529
|
||||||
msgid "Technical documentation / code"
|
msgid "Technical documentation / code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:479
|
#: includes/class-ai-translator-admin.php:530
|
||||||
msgid "GPT-4o / GPT-5"
|
msgid "GPT-4o / GPT-5"
|
||||||
msgstr ""
|
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."
|
msgid "Higher accuracy with variables, structured formats, and technical terminology."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:483
|
#: includes/class-ai-translator-admin.php:534
|
||||||
msgid "Long documents (100+ pages)"
|
msgid "Long documents (100+ pages)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:484
|
#: includes/class-ai-translator-admin.php:535
|
||||||
msgid "Gemini 2.5 Pro"
|
msgid "Gemini 2.5 Pro"
|
||||||
msgstr ""
|
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."
|
msgid "1M token context window. Maintains terminological consistency across long texts."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:488
|
#: includes/class-ai-translator-admin.php:539
|
||||||
msgid "High volume / low cost"
|
msgid "High volume / low cost"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:489
|
#: includes/class-ai-translator-admin.php:540
|
||||||
msgid "DeepSeek-V3"
|
msgid "DeepSeek-V3"
|
||||||
msgstr ""
|
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)."
|
msgid "Quality comparable to GPT-5 at ~$0.14/M tokens (20-50x cheaper than Claude/GPT)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:493
|
#: includes/class-ai-translator-admin.php:544
|
||||||
msgid "Rare / indigenous languages"
|
msgid "Rare / indigenous languages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:494
|
#: includes/class-ai-translator-admin.php:545
|
||||||
msgid "Claude Sonnet or Taskade Translate (multi-model routing)"
|
msgid "Claude Sonnet or Taskade Translate (multi-model routing)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:495
|
#: includes/class-ai-translator-admin.php:546
|
||||||
msgid "Better coverage for uncommon language pairs."
|
msgid "Better coverage for uncommon language pairs."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:498
|
#: includes/class-ai-translator-admin.php:549
|
||||||
msgid "Maximum coverage (133+ languages)"
|
msgid "Maximum coverage (133+ languages)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-admin.php:499
|
#: includes/class-ai-translator-admin.php:550
|
||||||
msgid "Google Cloud Translation"
|
msgid "Google Cloud Translation"
|
||||||
msgstr ""
|
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."
|
msgid "The most complete option, though with slightly lower quality on European languages."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -259,35 +292,38 @@ msgstr ""
|
||||||
msgid "The --post-id and --post-type flags are mutually exclusive."
|
msgid "The --post-id and --post-type flags are mutually exclusive."
|
||||||
msgstr ""
|
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.
|
#. translators: %s: locale code.
|
||||||
#: includes/class-ai-translator-cli.php:144
|
#: includes/class-ai-translator-cli.php:133
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "The locale %s is not installed on this site."
|
msgid "The locale %s is not installed on this site."
|
||||||
msgstr ""
|
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."
|
msgid "No translation fields are enabled in the plugin settings."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-cli.php:179
|
#: includes/class-ai-translator-cli.php:181
|
||||||
msgid "No posts matched the given criteria."
|
msgid "No posts matched the given criteria."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. translators: 1: success count, 2: failure count, 3: locale.
|
#. 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
|
#, php-format
|
||||||
msgid "%1$d translated, %2$d failed (locale: %3$s)."
|
msgid "%1$d translated, %2$d failed (locale: %3$s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -304,90 +340,70 @@ msgstr ""
|
||||||
msgid "The selected language is not installed on this site."
|
msgid "The selected language is not installed on this site."
|
||||||
msgstr ""
|
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."
|
msgid "No translatable fields were selected, or the requested fields are disabled in the settings."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-editor-ui.php:307
|
#: includes/class-ai-translator-editor-ui.php:321
|
||||||
msgid "The WordPress AI plugin is not active. Translation is unavailable."
|
#: includes/class-ai-translator-editor-ui.php:450
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: includes/class-ai-translator-editor-ui.php:309
|
|
||||||
#: includes/class-ai-translator-editor-ui.php:437
|
|
||||||
msgid "No translation fields are enabled in settings."
|
msgid "No translation fields are enabled in settings."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-editor-ui.php:311
|
#: includes/class-ai-translator-editor-ui.php:323
|
||||||
#: includes/class-ai-translator-editor-ui.php:436
|
#: includes/class-ai-translator-editor-ui.php:449
|
||||||
msgid "No languages are installed on this site."
|
msgid "No languages are installed on this site."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-editor-ui.php:314
|
#: includes/class-ai-translator-editor-ui.php:326
|
||||||
#: includes/class-ai-translator-editor-ui.php:432
|
#: includes/class-ai-translator-editor-ui.php:445
|
||||||
msgid "Target language"
|
msgid "Target language"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-editor-ui.php:324
|
#: includes/class-ai-translator-editor-ui.php:336
|
||||||
#: includes/class-ai-translator-editor-ui.php:430
|
#: includes/class-ai-translator-editor-ui.php:443
|
||||||
msgid "Translate"
|
msgid "Translate"
|
||||||
msgstr ""
|
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."
|
msgid "Save the post before translating to ensure the latest content is used. The translation replaces the current title and/or content."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-editor-ui.php:431
|
#: includes/class-ai-translator-editor-ui.php:444
|
||||||
msgid "Translating…"
|
msgid "Translating…"
|
||||||
msgstr ""
|
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."
|
msgid "Translation applied. Review and save the post."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-editor-ui.php:435
|
#: includes/class-ai-translator-editor-ui.php:448
|
||||||
msgid "Translation failed."
|
msgid "Translation failed."
|
||||||
msgstr ""
|
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."
|
msgid "You have unsaved changes. Save the post before translating to ensure the latest content is used."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-translator.php:82
|
#: includes/class-ai-translator-translator.php:102
|
||||||
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
|
|
||||||
msgid "The target language is not installed on this site."
|
msgid "The target language is not installed on this site."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. translators: %s: target language name.
|
#. translators: %s: target language name.
|
||||||
#: includes/class-ai-translator-translator.php:104
|
#: includes/class-ai-translator-translator.php:108
|
||||||
#, php-format
|
#, 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."
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: includes/class-ai-translator-translator.php:125
|
#: includes/class-ai-translator-translator.php:129
|
||||||
msgid "The AI service returned an unexpected response."
|
msgid "The AI service returned an unexpected response."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. translators: %s: WordPress AI plugin link.
|
#. translators: %s: Link to the WordPress AI settings page.
|
||||||
#: robotstxt-ai-translator.php:109
|
#: robotstxt-ai-translator.php:144
|
||||||
#, php-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: robotstxt-ai-translator.php:110
|
#: robotstxt-ai-translator.php:145
|
||||||
msgid "WordPress AI"
|
msgid "Settings → 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."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
|
||||||
22
readme.txt
22
readme.txt
|
|
@ -2,10 +2,9 @@
|
||||||
Contributors: robotstxt, javiercasares
|
Contributors: robotstxt, javiercasares
|
||||||
Tags: ai, translation, multilingual, multisite, editor
|
Tags: ai, translation, multilingual, multisite, editor
|
||||||
Requires at least: 7.0
|
Requires at least: 7.0
|
||||||
Tested up to: 7.0
|
Tested up to: 7.1
|
||||||
Requires PHP: 7.4
|
Requires PHP: 7.4
|
||||||
Requires Plugins: ai
|
Stable tag: 1.1.0
|
||||||
Stable tag: 1.0.0
|
|
||||||
License: GPL-3.0-or-later
|
License: GPL-3.0-or-later
|
||||||
License URI: https://www.gnu.org/licenses/gpl-3.0.txt
|
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 =
|
= 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.
|
* 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 =
|
= 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? =
|
= 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? =
|
= 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 ==
|
== Compatibility ==
|
||||||
|
|
||||||
* WordPress: 7.0
|
* WordPress: 7.0 - 7.1
|
||||||
* PHP: 7.4 - 8.5
|
* PHP: 7.4 - 8.5
|
||||||
* WP-CLI: 2.10 or newer
|
* WP-CLI: 2.12 or newer
|
||||||
|
|
||||||
== Changelog ==
|
== 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 =
|
= 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.
|
* 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 Name: AI Translator (by ROBOTSTXT)
|
||||||
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator
|
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator
|
||||||
* Gitea Plugin URI: 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.
|
* 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.0.0
|
* Version: 1.1.0
|
||||||
* Author: ROBOTSTXT
|
* Author: ROBOTSTXT
|
||||||
* Author URI: https://www.robotstxt.es/
|
* Author URI: https://www.robotstxt.es/
|
||||||
* License: GPL-3.0-or-later
|
* License: GPL-3.0-or-later
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
* Domain Path: /languages
|
* Domain Path: /languages
|
||||||
* Requires at least: 7.0
|
* Requires at least: 7.0
|
||||||
* Requires PHP: 7.4
|
* Requires PHP: 7.4
|
||||||
* Requires Plugins: ai
|
|
||||||
* Network: true
|
* Network: true
|
||||||
*
|
*
|
||||||
* @package ROBOTSTXT\AI_Translator
|
* @package ROBOTSTXT\AI_Translator
|
||||||
|
|
@ -26,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
/**
|
/**
|
||||||
* Plugin version.
|
* Plugin version.
|
||||||
*/
|
*/
|
||||||
define( 'AI_TRANSLATOR_VERSION', '1.0.0' );
|
define( 'AI_TRANSLATOR_VERSION', '1.1.0' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main plugin file path.
|
* 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-translator.php';
|
||||||
require_once AI_TRANSLATOR_DIR . 'includes/class-ai-translator-admin.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 . '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 ) {
|
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||||
require_once AI_TRANSLATOR_DIR . 'includes/class-ai-translator-cli.php';
|
require_once AI_TRANSLATOR_DIR . 'includes/class-ai-translator-cli.php';
|
||||||
|
|
@ -103,30 +106,43 @@ function ai_translator_bootstrap() {
|
||||||
$cli->register();
|
$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 );
|
Robotstxt_Updater::init( AI_TRANSLATOR_FILE );
|
||||||
}
|
}
|
||||||
add_action( 'plugins_loaded', 'ai_translator_bootstrap' );
|
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
|
* @since 1.0.0
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function ai_translator_dependency_notice() {
|
function ai_translator_dependency_notice() {
|
||||||
if ( function_exists( 'wp_ai_client_prompt' ) ) {
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! current_user_can( 'activate_plugins' ) ) {
|
$translator = new AI_Translator_Translator();
|
||||||
|
|
||||||
|
if ( $translator->is_supported() ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$settings_url = admin_url( 'options-general.php?page=options-connectors-wp-admin' );
|
||||||
|
|
||||||
$message = sprintf(
|
$message = sprintf(
|
||||||
/* translators: %s: WordPress AI plugin link. */
|
/* translators: %s: Link to the WordPress AI settings page. */
|
||||||
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' ),
|
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="https://wordpress.org/plugins/ai/" target="_blank" rel="noopener noreferrer">' . esc_html__( 'WordPress AI', 'robotstxt-ai-translator' ) . '</a>'
|
'<a href="' . esc_url( $settings_url ) . '">' . esc_html__( 'Settings → AI', 'robotstxt-ai-translator' ) . '</a>'
|
||||||
);
|
);
|
||||||
|
|
||||||
printf(
|
printf(
|
||||||
|
|
@ -136,8 +152,6 @@ function ai_translator_dependency_notice() {
|
||||||
array(
|
array(
|
||||||
'a' => array(
|
'a' => array(
|
||||||
'href' => 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)",
|
"name": "AI Translator (by ROBOTSTXT)",
|
||||||
"slug": "robotstxt-ai-translator",
|
"slug": "robotstxt-ai-translator",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator/releases/download/1.0.0/robotstxt-ai-translator-1.0.0.zip",
|
"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": "7.0",
|
||||||
"requires_php": "7.4",
|
"requires_php": "7.4",
|
||||||
"tested": "7.0",
|
"tested": "7.1",
|
||||||
"last_updated": "2026-05-23",
|
"last_updated": "2026-05-25",
|
||||||
"author": "ROBOTSTXT",
|
"author": "ROBOTSTXT",
|
||||||
"author_profile": "https://www.robotstxt.es/",
|
"author_profile": "https://www.robotstxt.es/",
|
||||||
"homepage": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-ai-translator",
|
"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.",
|
"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.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>",
|
"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": {
|
"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.",
|
"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.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>"
|
"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": {
|
"banners": {
|
||||||
"low": "",
|
"low": "",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue