Making WordPress.org

Changeset 12510


Ignore:
Timestamp:
03/29/2023 02:12:17 PM (3 years ago)
Author:
amieiro
Message:

Translate: Add suggestions from OpenAI and DeepL

Location:
sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-suggestions
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-suggestions/css/translation-suggestions.css

    r12312 r12510  
    4343}
    4444
     45.openai-suggestion__score {
     46        display: flex;
     47        align-items: center;
     48        justify-content: center;
     49        margin-right: 10px;
     50        border-radius: 100%;
     51        background: #F2F0F7;
     52        color: #4E426C;
     53        font-size: 12px;
     54        height: 3em;
     55        width: 5em;
     56}
     57
     58.deepl-suggestion__score {
     59        display: flex;
     60        align-items: center;
     61        justify-content: center;
     62        margin-right: 10px;
     63        border-radius: 100%;
     64        background: #F2F0F7;
     65        color: #4E426C;
     66        font-size: 12px;
     67        height: 3em;
     68        width: 4em;
     69}
     70
    4571.translation-suggestion__translation {
    4672        flex: 1;
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-suggestions/inc/class-plugin.php

    r11732 r12510  
    9999                GP::$router->prepend( "/$set/-get-tm-suggestions", [ __NAMESPACE__ . '\Routes\Translation_Memory', 'get_suggestions' ] );
    100100                GP::$router->prepend( "/$set/-get-other-language-suggestions", [ __NAMESPACE__ . '\Routes\Other_Languages', 'get_suggestions' ] );
     101                GP::$router->prepend( "/-save-external-suggestions", [ __NAMESPACE__ . '\Routes\Translation_Memory', 'update_external_translations' ], 'post' );
    101102        }
    102103
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-suggestions/inc/routes/class-translation-memory.php

    r8749 r12510  
    44
    55use GP;
     6use GP_Locales;
    67use GP_Route;
    78use WordPressdotorg\GlotPress\TranslationSuggestions\Translation_Memory_Client;
     
    1112
    1213        public function get_suggestions( $project_path, $locale_slug, $set_slug ) {
    13                 $original_id = gp_get( 'original' );
    14                 $nonce       = gp_get( 'nonce' );
     14                $original_id                           = gp_get( 'original' );
     15                $translation_id                        = gp_get( 'translation', 0 );
     16                $nonce                                 = gp_get( 'nonce' );
     17                $gp_default_sort                       = get_user_option( 'gp_default_sort' );
     18                $external_services_exclude_some_status = gp_array_get( $gp_default_sort, 'external_services_exclude_some_status', 0 );
     19                $translation                           = null;
     20                $openai_suggestions                    = array();
     21                $deepl_suggestions                     = array();
    1522
    1623                if ( ! wp_verify_nonce( $nonce, 'translation-memory-suggestions-' . $original_id ) ) {
     
    3239                }
    3340
    34                 $suggestions = Translation_Memory_Client::query( $original->singular, $locale );
     41                $suggestions                     = Translation_Memory_Client::query( $original->singular, $locale );
     42                $current_set_slug                = 'default';
     43                $locale_glossary_translation_set = GP::$translation_set->by_project_id_slug_and_locale( 0, $current_set_slug, $locale_slug );
     44                $locale_glossary                 = GP::$glossary->by_set_id( $locale_glossary_translation_set->id );
     45
     46                if ( $external_services_exclude_some_status ) {
     47                        if ( $translation_id > 0 ) {
     48                                $translation = GP::$translation->get( $translation_id );
     49                        }
     50                        if ( ! $translation || ( 'current' != $translation->status && 'rejected' != $translation->status && 'old' != $translation->status ) ) {
     51                                $openai_suggestions = $this->get_openai_suggestion( $original->singular, $locale_slug, $locale_glossary );
     52                                $deepl_suggestions  = $this->get_deepl_suggestion( $original->singular, $locale_slug, $set_slug );
     53                        }
     54                } else {
     55                        $openai_suggestions = $this->get_openai_suggestion( $original->singular, $locale_slug, $locale_glossary );
     56                        $deepl_suggestions  = $this->get_deepl_suggestion( $original->singular, $locale_slug, $set_slug );
     57                }
    3558
    3659                if ( is_wp_error( $suggestions ) ) {
     
    3861                }
    3962
    40                 wp_send_json_success( gp_tmpl_get_output( 'translation-memory-suggestions', [ 'suggestions' => $suggestions ], PLUGIN_DIR . '/templates/' ) );
     63                wp_send_json_success( gp_tmpl_get_output( 'translation-memory-suggestions', compact( 'suggestions', 'openai_suggestions', 'deepl_suggestions' ), PLUGIN_DIR . '/templates/' ) );
     64        }
     65
     66        /**
     67         * Get suggestions from OpenAI (ChatGPT).
     68         *
     69         * @param string       $original_singular The singular from the original string.
     70         * @param string       $locale            The locale.
     71         * @param \GP_Glossary $locale_glossary   The glossary for the locale.
     72         *
     73         * @return array
     74         */
     75        private function get_openai_suggestion( $original_singular, $locale, $locale_glossary ): array {
     76                $openai_query    = '';
     77                $glossary_query  = '';
     78                $gp_default_sort = get_user_option( 'gp_default_sort' );
     79                $openai_key      = gp_array_get( $gp_default_sort, 'openai_api_key' );
     80                if ( empty( trim( $openai_key ) ) ) {
     81                        return array();
     82                }
     83                $openai_prompt      = gp_array_get( $gp_default_sort, 'openai_custom_prompt' );
     84                $openai_temperature = gp_array_get( $gp_default_sort, 'openai_temperature', 0 );
     85                if ( ! is_float( $openai_temperature ) || $openai_temperature < 0 || $openai_temperature > 2 ) {
     86                        $openai_temperature = 0;
     87                }
     88
     89                $glossary_entries = array();
     90                foreach ( $locale_glossary->get_entries() as $gp_glossary_entry ) {
     91                        if ( strpos( strtolower( $original_singular ), strtolower( $gp_glossary_entry->term ) ) !== false ) {
     92                                // Use the translation as key, because we could have multiple translations with the same term.
     93                                $glossary_entries[ $gp_glossary_entry->translation ] = $gp_glossary_entry->term;
     94                        }
     95                }
     96                if ( ! empty( $glossary_entries ) ) {
     97                        $glossary_query = ' The following terms are translated as follows: ';
     98                        foreach ( $glossary_entries as $translation => $term ) {
     99                                $glossary_query .= '"' . $term . '" is translated as "' . $translation . '"';
     100                                if ( array_key_last( $glossary_entries ) != $translation ) {
     101                                        $glossary_query .= ', ';
     102                                }
     103                        }
     104                        $glossary_query .= '.';
     105                }
     106
     107                $gp_locale     = GP_Locales::by_field( 'slug', $locale );
     108                $openai_query .= ' Translate the following text to ' . $gp_locale->english_name . ": \n";
     109                $openai_query .= '"' . $original_singular . '"';
     110
     111                $messages = array(
     112                        array(
     113                                'role'    => 'system',
     114                                'content' => $openai_prompt . $glossary_query,
     115                        ),
     116                        array(
     117                                'role'    => 'user',
     118                                'content' => $openai_query,
     119                        ),
     120                );
     121
     122                $openai_response = wp_remote_post(
     123                        'https://api.openai.com/v1/chat/completions',
     124                        array(
     125                                'timeout' => 20,
     126                                'headers' => array(
     127                                        'Content-Type'  => 'application/json',
     128                                        'Authorization' => 'Bearer ' . $openai_key,
     129                                ),
     130                                'body'    => wp_json_encode(
     131                                        array(
     132                                                'model'       => 'gpt-3.5-turbo',
     133                                                'max_tokens'  => 1000,
     134                                                'n'           => 1,
     135                                                'messages'    => $messages,
     136                                                'temperature' => $openai_temperature,
     137                                        )
     138                                ),
     139                        )
     140                );
     141                if ( is_wp_error( $openai_response ) ) {
     142                        return array();
     143                }
     144                $response_status = wp_remote_retrieve_response_code( $openai_response );
     145                if ( 200 !== $response_status ) {
     146                        return array();
     147                }
     148                $output = json_decode( wp_remote_retrieve_body( $openai_response ), true );
     149                $this->update_openai_tokens_used( $output['usage']['total_tokens'] );
     150
     151                $message                           = $output['choices'][0]['message'];
     152                $response['openai']['translation'] = trim( trim( $message['content'] ), '"' );
     153                $response['openai']['diff']        = '';
     154
     155                return $response;
     156        }
     157
     158        /**
     159         * Updates the number of tokens used by OpenAI.
     160         *
     161         * @param int $tokens_used The number of tokens used.
     162         */
     163        private function update_openai_tokens_used( int $tokens_used ) {
     164                $gp_external_translations = get_user_option( 'gp_external_translations' );
     165                $openai_tokens_used       = gp_array_get( $gp_external_translations, 'openai_tokens_used' );
     166                if ( ! is_int( $openai_tokens_used ) || $openai_tokens_used < 0 ) {
     167                        $openai_tokens_used = 0;
     168                }
     169                $openai_tokens_used                            += $tokens_used;
     170                $gp_external_translations['openai_tokens_used'] = $openai_tokens_used;
     171                update_user_option( get_current_user_id(), 'gp_external_translations', $gp_external_translations );
     172        }
     173
     174        /**
     175         * Gets a translation suggestion from DeepL.
     176         *
     177         * @param string $original_singular The singular from the original string.
     178         * @param string $locale            The locale.
     179         * @param string $set_slug          The set slug.
     180         *
     181         * @return array
     182         */
     183        private function get_deepl_suggestion( string $original_singular, string $locale, string $set_slug ): array {
     184                $free_url        = 'https://api-free.deepl.com/v2/translate';
     185                $gp_default_sort = get_user_option( 'gp_default_sort' );
     186                $deepl_api_key   = gp_array_get( $gp_default_sort, 'deepl_api_key' );
     187                if ( empty( trim( $deepl_api_key ) ) ) {
     188                        return array();
     189                }
     190                $target_lang = $this->get_deepl_locale( $locale );
     191                if ( empty( $target_lang ) ) {
     192                        return array();
     193                }
     194                $deepl_response = wp_remote_post(
     195                        $free_url,
     196                        array(
     197                                'timeout' => 20,
     198                                'body'    => array(
     199                                        'auth_key'    => $deepl_api_key,
     200                                        'text'        => $original_singular,
     201                                        'source_lang' => 'EN',
     202                                        'target_lang' => $target_lang,
     203                                        'formality'   => $this->get_language_formality( $target_lang, $set_slug ),
     204                                ),
     205                        ),
     206                );
     207                if ( is_wp_error( $deepl_response ) ) {
     208                        return array();
     209                } else {
     210                        $body                             = wp_remote_retrieve_body( $deepl_response );
     211                        $response['deepl']['translation'] = json_decode( $body )->translations[0]->text;
     212                        $response['deepl']['diff']        = '';
     213                        $this->update_deepl_chars_used( $original_singular );
     214                        return $response;
     215                }
     216        }
     217
     218        /**
     219         * Updates the number of characters used by DeepL.
     220         *
     221         * @param string $original_singular The singular from the original string.
     222         */
     223        private function update_deepl_chars_used( string $original_singular ) {
     224                $gp_external_translations = get_user_option( 'gp_external_translations' );
     225                $deepl_chars_used         = gp_array_get( $gp_external_translations, 'deepl_chars_used', 0 );
     226                if ( ! is_int( $deepl_chars_used ) || $deepl_chars_used < 0 ) {
     227                        $deepl_chars_used = 0;
     228                }
     229                $deepl_chars_used                            += mb_strlen( $original_singular );
     230                $gp_external_translations['deepl_chars_used'] = $deepl_chars_used;
     231                update_user_option( get_current_user_id(), 'gp_external_translations', $gp_external_translations );
     232        }
     233
     234        /**
     235         * Gets the Deepl locale.
     236         *
     237         * @param string $locale The WordPress locale.
     238         *
     239         * @return string
     240         */
     241        private function get_deepl_locale( string $locale ): string {
     242                $available_locales = array(
     243                        'bg'    => 'BG',
     244                        'cs'    => 'CS',
     245                        'da'    => 'DA',
     246                        'de'    => 'DE',
     247                        'el'    => 'EL',
     248                        'en-gb' => 'EN-GB',
     249                        'es'    => 'ES',
     250                        'et'    => 'ET',
     251                        'fi'    => 'FI',
     252                        'fr'    => 'FR',
     253                        'hu'    => 'HU',
     254                        'id'    => 'ID',
     255                        'it'    => 'IT',
     256                        'ja'    => 'JA',
     257                        'ko'    => 'KO',
     258                        'lt'    => 'LT',
     259                        'lv'    => 'LV',
     260                        'nb'    => 'NB',
     261                        'nl'    => 'NL',
     262                        'pl'    => 'PL',
     263                        'pt'    => 'PT-PT',
     264                        'pt-br' => 'PT-BR',
     265                        'ro'    => 'RO',
     266                        'ru'    => 'RU',
     267                        'sk'    => 'SK',
     268                        'sl'    => 'SL',
     269                        'sv'    => 'SV',
     270                        'tr'    => 'TR',
     271                        'uk'    => 'UK',
     272                        'zh-cn' => 'ZH',
     273                );
     274                if ( array_key_exists( $locale, $available_locales ) ) {
     275                        return $available_locales[ $locale ];
     276                }
     277                return '';
     278        }
     279
     280        /**
     281         * Gets the formality of the language.
     282         *
     283         * @param string $locale   The locale.
     284         * @param string $set_slug The set slug.
     285         *
     286         * @return string
     287         */
     288        private function get_language_formality( string $locale, string $set_slug ): string {
     289                $lang_informality = array(
     290                        'BG'    => 'prefer_more',
     291                        'CS'    => 'prefer_less',
     292                        'DA'    => 'prefer_less',
     293                        'DE'    => 'prefer_less',
     294                        'EL'    => 'prefer_more',
     295                        'EN-GB' => 'prefer_less',
     296                        'ES'    => 'prefer_less',
     297                        'ET'    => 'prefer_less',
     298                        'FI'    => 'prefer_less',
     299                        'FR'    => 'prefer_more',
     300                        'HU'    => 'prefer_more',
     301                        'ID'    => 'prefer_more',
     302                        'IT'    => 'prefer_less',
     303                        'JA'    => 'prefer_more',
     304                        'KO'    => 'prefer_less',
     305                        'LT'    => 'prefer_more',
     306                        'LV'    => 'prefer_less',
     307                        'NB'    => 'prefer_less',
     308                        'NL'    => 'prefer_less',
     309                        'PL'    => 'prefer_less',
     310                        'PT-BR' => 'prefer_less',
     311                        'PT-PT' => 'prefer_more',
     312                        'RO'    => 'prefer_less',
     313                        'RU'    => 'prefer_more',
     314                        'SK'    => 'prefer_less',
     315                        'SL'    => 'prefer_less',
     316                        'SV'    => 'prefer_less',
     317                        'TR'    => 'prefer_less',
     318                        'UK'    => 'prefer_more',
     319                        'ZH'    => 'prefer_more',
     320                );
     321
     322                if ( ( 'DE' == $locale || 'NL' == $locale ) && 'formal' == $set_slug ) {
     323                        return 'prefer_more';
     324                }
     325                if ( array_key_exists( $locale, $lang_informality ) ) {
     326                        return $lang_informality[ $locale ];
     327                }
     328
     329                return 'default';
     330        }
     331
     332        /**
     333         * Updates the external translations used by each user.
     334         *
     335         * @return void
     336         */
     337        public function update_external_translations() {
     338                if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'wporg-editor-settings' ) ) {
     339                        wp_send_json_error( array( 'message' => esc_html__( 'Invalid nonce.', 'glotpress' ) ), 403 );
     340                }
     341                if ( ! isset( $_POST['translation'] ) ) {
     342                        wp_send_json_error( array( 'message' => esc_html__( 'Translation parameter is not present.', 'glotpress' ) ), 400 );
     343                }
     344                if ( ! isset( $_POST['openAITranslationsUsed'] ) && ! isset( $_POST['deeplTranslationsUsed'] ) ) {
     345                        wp_send_json_error( array( 'message' => esc_html__( 'Translation suggested parameter is not present.', 'glotpress' ) ), 400 );
     346                }
     347                if ( isset( $_POST['openAITranslationsUsed'] ) ) {
     348                        $this->update_one_external_translation(
     349                                $_POST['translation'],
     350                                $_POST['openAITranslationsUsed'],
     351                                'openai_translations_used',
     352                                'openai_same_translations_used'
     353                        );
     354                }
     355                if ( isset( $_POST['deeplTranslationsUsed'] ) ) {
     356                        $this->update_one_external_translation(
     357                                $_POST['translation'],
     358                                $_POST['deeplTranslationsUsed'],
     359                                'deepl_translations_used',
     360                                'deepl_same_translations_used'
     361                        );
     362                }
     363                wp_send_json_success();
     364        }
     365
     366        /**
     367         * Updates an external translation used by each user.
     368         *
     369         * @param string $translation                     The translation.
     370         * @param string $suggestion                      The suggestion.
     371         * @param string $external_translations_used      The external translations used.
     372         * @param string $external_same_translations_used The external same translations used.
     373         *
     374         * @return void
     375         */
     376        private function update_one_external_translation( string $translation, string $suggestion, string $external_translations_used, string $external_same_translations_used ) {
     377                $sameTranslationUsed      = $translation == $suggestion;
     378                $gp_external_translations = get_user_option( 'gp_external_translations' );
     379                $translations_used        = gp_array_get( $gp_external_translations, $external_translations_used, 0 );
     380                $same_translations_used   = gp_array_get( $gp_external_translations, $external_same_translations_used, 0 );
     381                if ( ! is_int( $translations_used ) || $translations_used < 0 ) {
     382                        $translations_used = 0;
     383                }
     384                $translations_used++;
     385                $gp_external_translations[ $external_translations_used ] = $translations_used;
     386                if ( $sameTranslationUsed ) {
     387                        if ( ! is_int( $same_translations_used ) || $same_translations_used < 0 ) {
     388                                $same_translations_used = 0;
     389                        }
     390                        $same_translations_used++;
     391                        $gp_external_translations[ $external_same_translations_used ] = $same_translations_used;
     392                }
     393                update_user_option( get_current_user_id(), 'gp_external_translations', $gp_external_translations );
    41394        }
    42395}
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-suggestions/js/translation-suggestions.js

    r8937 r12510  
    11( function( $ ){
    2         function fetchSuggestions( $container, apiUrl, originalId, nonce ) {
     2        function fetchSuggestions( $container, apiUrl, originalId, translationId, nonce ) {
    33                var xhr = $.ajax( {
    44                        url: apiUrl,
    55                        data: {
    66                                'original': originalId,
     7                                'translation': translationId,
    78                                'nonce': nonce
    89                        },
     
    4546
    4647                var originalId = $gp.editor.current.original_id;
     48                var translationId = $gp.editor.current.translation_id;
    4749                var nonce = $container.data( 'nonce' );
    4850
    49                 fetchSuggestions( $container, window.WPORG_TRANSLATION_MEMORY_API_URL, originalId, nonce );
     51                fetchSuggestions( $container, window.WPORG_TRANSLATION_MEMORY_API_URL, originalId, translationId, nonce );
    5052        }
    5153
     
    6365
    6466                var originalId = $gp.editor.current.original_id;
     67                var translationId = $gp.editor.current.translation_id;
    6568                var nonce = $container.data( 'nonce' );
    6669
    67                 fetchSuggestions( $container, window.WPORG_OTHER_LANGUAGES_API_URL, originalId , nonce );
     70                fetchSuggestions( $container, window.WPORG_OTHER_LANGUAGES_API_URL, originalId , translationId,  nonce );
    6871        }
    6972
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-translation-suggestions/templates/translation-memory-suggestions.php

    r11732 r12510  
    11<?php
    2 
    3 if ( empty( $suggestions ) ) {
     2if ( empty( $suggestions ) && empty( $openai_suggestions ) && empty( $deepl_suggestions ) ) {
    43        echo '<p class="no-suggestions">No suggestions.</p>';
    54} else {
     
    2423                echo '</li>';
    2524        }
     25        foreach ( $openai_suggestions as $suggestion ) {
     26                echo '<li>';
     27                echo '<div class="translation-suggestion with-tooltip openai" tabindex="0" role="button" aria-pressed="false" aria-label="Copy translation">';
     28                        echo '<span class="openai-suggestion__score">OpenAI</span>';
     29
     30                        echo '<span class="translation-suggestion__translation">';
     31                                echo esc_translation( $suggestion['translation'] );
     32
     33                                if ( $suggestion['diff'] ) {
     34                                        echo '<span class="translation-suggestion__original-diff">' . wp_kses_post( $suggestion['diff'] ) . '</span>';
     35                                }
     36                        echo '</span>';
     37
     38                        echo '<span aria-hidden="true" class="translation-suggestion__translation-raw">' . esc_translation( $suggestion['translation'] ) . '</span>';
     39
     40                        echo '<button type="button" class="button is-small copy-suggestion">Copy</button>';
     41                echo '</div>';
     42                echo '</li>';
     43        }
     44        foreach ( $deepl_suggestions as $suggestion ) {
     45                echo '<li>';
     46                echo '<div class="translation-suggestion with-tooltip deepl" tabindex="0" role="button" aria-pressed="false" aria-label="Copy translation">';
     47                        echo '<span class="deepl-suggestion__score">DeepL</span>';
     48
     49                        echo '<span class="translation-suggestion__translation">';
     50                                echo esc_translation( $suggestion['translation'] );
     51
     52                                if ( $suggestion['diff'] ) {
     53                                        echo '<span class="translation-suggestion__original-diff">' . wp_kses_post( $suggestion['diff'] ) . '</span>';
     54                                }
     55                        echo '</span>';
     56
     57                        echo '<span aria-hidden="true" class="translation-suggestion__translation-raw">' . esc_translation( $suggestion['translation'] ) . '</span>';
     58
     59                        echo '<button type="button" class="button is-small copy-suggestion">Copy</button>';
     60                echo '</div>';
     61                echo '</li>';
     62        }
    2663        echo '</ul>';
    2764}
Note: See TracChangeset for help on using the changeset viewer.