v1.2.0
This commit is contained in:
parent
02cd01e862
commit
1ccc13da01
14 changed files with 845 additions and 197 deletions
|
|
@ -119,6 +119,41 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
|||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
AI_TRANSLATOR_REST_NAMESPACE,
|
||||
'/translate-text',
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'handle_translate_text_request' ),
|
||||
'permission_callback' => array( $this, 'check_translate_permissions' ),
|
||||
'args' => array(
|
||||
'post_id' => array(
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
'minimum' => 1,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
),
|
||||
'text' => array(
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'minLength' => 1,
|
||||
'maxLength' => 20000,
|
||||
'sanitize_callback' => array( $this, 'sanitize_raw_text' ),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
),
|
||||
'target_locale' => array(
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'pattern' => '^[A-Za-z]{2,3}(_[A-Za-z0-9]{2,8})?$',
|
||||
'maxLength' => 20,
|
||||
'sanitize_callback' => array( $this, 'sanitize_locale' ),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,6 +176,79 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
|||
return substr( $value, 0, 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitises a raw text value without stripping HTML.
|
||||
*
|
||||
* The /translate-text endpoint accepts HTML content (Gutenberg block markup,
|
||||
* Classic editor HTML, etc.). Standard WordPress text sanitisers strip HTML
|
||||
* tags; this method only casts to string and preserves markup intact.
|
||||
* Security is enforced by the permission_callback (requires edit_post capability).
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param mixed $value Raw value from the request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sanitize_raw_text( $value ) {
|
||||
return is_string( $value ) ? $value : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* REST callback for the translate-text endpoint.
|
||||
*
|
||||
* Translates a raw text string (HTML or plain text) into the target locale.
|
||||
* The post_id parameter is used solely for authorisation; no post content is
|
||||
* read from the database. This endpoint is designed for client-side chunked
|
||||
* translation where the caller supplies the text directly.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function handle_translate_text_request( WP_REST_Request $request ) {
|
||||
$post_id_param = $request->get_param( 'post_id' );
|
||||
$text_param = $request->get_param( 'text' );
|
||||
$target_locale_param = $request->get_param( 'target_locale' );
|
||||
|
||||
$post_id = is_numeric( $post_id_param ) ? (int) $post_id_param : 0;
|
||||
$text = is_string( $text_param ) ? $text_param : '';
|
||||
$target_locale = is_string( $target_locale_param ) ? $target_locale_param : '';
|
||||
|
||||
if ( '' === trim( $text ) ) {
|
||||
return new WP_REST_Response( array( 'text' => '' ), 200 );
|
||||
}
|
||||
|
||||
// Verify the post exists (post_id is already authorised by the permission callback).
|
||||
$post = get_post( $post_id );
|
||||
if ( ! $post instanceof WP_Post ) {
|
||||
return new WP_Error(
|
||||
'ai_translator_post_not_found',
|
||||
__( 'Post not found.', 'robotstxt-ai-translator' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
// Validate the locale is installed on this site.
|
||||
$installed = $this->translator->get_installed_locales();
|
||||
if ( ! in_array( $target_locale, $installed, true ) ) {
|
||||
return new WP_Error(
|
||||
'ai_translator_invalid_locale',
|
||||
__( 'The selected language is not installed on this site.', 'robotstxt-ai-translator' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$translated = $this->translator->translate( $text, $target_locale );
|
||||
if ( is_wp_error( $translated ) ) {
|
||||
return $this->error_with_status( $translated, 502 );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( array( 'text' => $translated ), 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* REST permission callback for the translate endpoint.
|
||||
*
|
||||
|
|
@ -428,31 +536,35 @@ if ( ! class_exists( 'AI_Translator_Editor_UI' ) ) {
|
|||
$settings = $this->settings->get_settings();
|
||||
|
||||
$data = array(
|
||||
'restNamespace' => AI_TRANSLATOR_REST_NAMESPACE,
|
||||
'restRoute' => '/translate',
|
||||
'context' => $context,
|
||||
'postId' => $post_id,
|
||||
'available' => $this->translator->is_available(),
|
||||
'languages' => $this->get_language_list(),
|
||||
'settings' => array(
|
||||
'restNamespace' => AI_TRANSLATOR_REST_NAMESPACE,
|
||||
'restRoute' => '/translate',
|
||||
'chunkTextRoute' => '/translate-text',
|
||||
'chunkSize' => 5000,
|
||||
'context' => $context,
|
||||
'postId' => $post_id,
|
||||
'available' => $this->translator->is_available(),
|
||||
'languages' => $this->get_language_list(),
|
||||
'settings' => array(
|
||||
'translateTitle' => ! empty( $settings['translate_title'] ),
|
||||
'translateContent' => ! empty( $settings['translate_content'] ),
|
||||
'translateExcerpt' => ! empty( $settings['translate_excerpt'] ),
|
||||
),
|
||||
'i18n' => array(
|
||||
'translate' => __( 'Translate', 'robotstxt-ai-translator' ),
|
||||
'translating' => __( 'Translating…', 'robotstxt-ai-translator' ),
|
||||
'targetLanguage' => __( 'Target language', 'robotstxt-ai-translator' ),
|
||||
'panelTitle' => __( 'AI Translator', 'robotstxt-ai-translator' ),
|
||||
'success' => __( 'Translation applied. Review and save the post.', 'robotstxt-ai-translator' ),
|
||||
'genericError' => __( 'Translation failed.', 'robotstxt-ai-translator' ),
|
||||
'noLanguages' => __( 'No languages are installed on this site.', 'robotstxt-ai-translator' ),
|
||||
'noFields' => __( 'No translation fields are enabled in settings.', 'robotstxt-ai-translator' ),
|
||||
'aiUnavailable' => __( 'AI features are disabled for this WordPress installation.', 'robotstxt-ai-translator' ),
|
||||
'saveBeforeWarn' => __( 'You have unsaved changes. Save the post before translating to ensure the latest content is used.', 'robotstxt-ai-translator' ),
|
||||
'translateTitle' => __( 'Translate title', 'robotstxt-ai-translator' ),
|
||||
'translateContent' => __( 'Translate content', 'robotstxt-ai-translator' ),
|
||||
'translateExcerpt' => __( 'Translate excerpt', 'robotstxt-ai-translator' ),
|
||||
'i18n' => array(
|
||||
'translate' => __( 'Translate', 'robotstxt-ai-translator' ),
|
||||
'translating' => __( 'Translating…', 'robotstxt-ai-translator' ),
|
||||
/* translators: 1: number of chunks translated so far, 2: total number of chunks. */
|
||||
'translatingProgress' => __( 'Translating… ({done}/{total})', 'robotstxt-ai-translator' ),
|
||||
'targetLanguage' => __( 'Target language', 'robotstxt-ai-translator' ),
|
||||
'panelTitle' => __( 'AI Translator', 'robotstxt-ai-translator' ),
|
||||
'success' => __( 'Translation applied. Review and save the post.', 'robotstxt-ai-translator' ),
|
||||
'genericError' => __( 'Translation failed.', 'robotstxt-ai-translator' ),
|
||||
'noLanguages' => __( 'No languages are installed on this site.', 'robotstxt-ai-translator' ),
|
||||
'noFields' => __( 'No translation fields are enabled in settings.', 'robotstxt-ai-translator' ),
|
||||
'aiUnavailable' => __( 'AI features are disabled for this WordPress installation.', 'robotstxt-ai-translator' ),
|
||||
'saveBeforeWarn' => __( 'You have unsaved changes. Save the post before translating to ensure the latest content is used.', 'robotstxt-ai-translator' ),
|
||||
'translateTitle' => __( 'Translate title', 'robotstxt-ai-translator' ),
|
||||
'translateContent' => __( 'Translate content', 'robotstxt-ai-translator' ),
|
||||
'translateExcerpt' => __( 'Translate excerpt', 'robotstxt-ai-translator' ),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue