diff options
Diffstat (limited to 'www/wiki/extensions/Translate/resources/js/ext.translate.editor.helpers.js')
-rw-r--r-- | www/wiki/extensions/Translate/resources/js/ext.translate.editor.helpers.js | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/www/wiki/extensions/Translate/resources/js/ext.translate.editor.helpers.js b/www/wiki/extensions/Translate/resources/js/ext.translate.editor.helpers.js new file mode 100644 index 00000000..38cb9a46 --- /dev/null +++ b/www/wiki/extensions/Translate/resources/js/ext.translate.editor.helpers.js @@ -0,0 +1,542 @@ +/*! + * Translate editor additional helper functionality + */ +( function () { + 'use strict'; + + var translateEditorHelpers = { + + showDocumentationEditor: function () { + var $infoColumnBlock = this.$editor.find( '.infocolumn-block' ), + $editColumn = this.$editor.find( '.editcolumn' ), + $messageDescEditor = $infoColumnBlock.find( '.message-desc-editor' ), + $messageDescViewer = $infoColumnBlock.find( '.message-desc-viewer' ); + + $infoColumnBlock + .removeClass( 'five' ) + .addClass( 'seven' ); + $editColumn + .removeClass( 'seven' ) + .addClass( 'five' ); + + $messageDescViewer.addClass( 'hide' ); + + $messageDescEditor.removeClass( 'hide' ); + $messageDescEditor.find( '.tux-textarea-documentation' ).focus(); + + // So that the link won't be followed + return false; + }, + + hideDocumentationEditor: function () { + var $infoColumnBlock = this.$editor.find( '.infocolumn-block' ), + $editColumn = this.$editor.find( '.editcolumn' ), + $messageDescEditor = $infoColumnBlock.find( '.message-desc-editor' ), + $messageDescViewer = $infoColumnBlock.find( '.message-desc-viewer' ); + + $infoColumnBlock + .removeClass( 'seven' ) + .addClass( 'five' ); + $editColumn + .removeClass( 'five' ) + .addClass( 'seven' ); + + $messageDescEditor.addClass( 'hide' ); + $messageDescViewer.removeClass( 'hide' ); + }, + + /** + * Save the documentation + * + * @return {jQuery.Promise} + */ + saveDocumentation: function () { + var translateEditor = this, + api = new mw.Api(), + newDocumentation = translateEditor.$editor.find( '.tux-textarea-documentation' ).val(); + + return api.postWithToken( 'csrf', { + action: 'edit', + title: translateEditor.message.title + .replace( /\/[a-z-]+$/, '/' + mw.config.get( 'wgTranslateDocumentationLanguageCode' ) ), + text: newDocumentation + } ).done( function ( response ) { + var $messageDesc = translateEditor.$editor.find( '.infocolumn-block .message-desc' ); + + if ( response.edit.result === 'Success' ) { + api.parse( + newDocumentation + ).done( function ( parsedDocumentation ) { + $messageDesc.html( parsedDocumentation ); + } ).fail( function ( errorCode, results ) { + $messageDesc.html( newDocumentation ); + mw.log( 'Error parsing documentation ' + errorCode + ' ' + results.error.info ); + } ); + // A collapsible element may have been added + $( '.mw-identical-title' ).makeCollapsible(); + + translateEditor.hideDocumentationEditor(); + } else { + mw.notify( 'Error saving message documentation' ); + mw.log( 'Error saving documentation', response ); + } + } ).fail( function ( errorCode, results ) { + mw.notify( 'Error saving message documentation' ); + mw.log( 'Error saving documentation', errorCode, results ); + } ); + }, + + /** + * Shows the message documentation. + * + * @param {Object} documentation A documentation object as returned by API. + */ + showMessageDocumentation: function ( documentation ) { + var $descEditLink, + documentationDir, + expand, + $messageDescViewer, + $messageDoc, + readMore, + langAttr, + $readMore = null; + + if ( !mw.config.get( 'wgTranslateDocumentationLanguageCode' ) ) { + return; + } + + $messageDescViewer = this.$editor.find( '.message-desc-viewer' ); + $descEditLink = $messageDescViewer.find( '.message-desc-edit' ); + $messageDoc = $messageDescViewer.find( '.message-desc' ); + + // Display the documentation only if it's not empty and + // documentation language is configured + if ( documentation.error ) { + // TODO: better error handling, especially since the presence of documentation + // is heavily hinted at in the UI + return; + } else if ( documentation.value ) { + documentationDir = $.uls.data.getDir( documentation.language ); + + // Show the documentation and set appropriate + // lang and dir attributes. + // The message documentation is assumed to be written + // in the content language of the wiki. + langAttr = { + lang: documentation.language, + dir: documentationDir + }; + + // Possible classes: + // * mw-content-ltr + // * mw-content-rtl + // (The direction classes are needed, because the documentation + // is likely to be MediaWiki-formatted text.) + $messageDoc + .attr( langAttr ) + .addClass( 'mw-content-' + documentationDir ) + .html( documentation.html ); + + $messageDoc.find( 'a[href]' ).prop( 'target', '_blank' ); + + this.$editor.find( '.tux-textarea-documentation' ) + .attr( langAttr ) + .val( documentation.value ); + + $descEditLink.text( mw.msg( 'tux-editor-edit-desc' ) ); + + if ( documentation.html.length > 500 ) { + expand = function () { + $messageDoc.removeClass( 'compact' ); + $readMore.text( mw.msg( 'tux-editor-message-desc-less' ) ); + }; + + readMore = function () { + if ( $messageDoc.hasClass( 'compact' ) ) { + expand(); + } else { + $messageDoc.addClass( 'compact' ); + $readMore.text( mw.msg( 'tux-editor-message-desc-more' ) ); + } + }; + + $readMore = $( '<span>' ) + .addClass( 'read-more column' ) + .text( mw.msg( 'tux-editor-message-desc-more' ) ) + .click( readMore ); + + $messageDescViewer.find( '.message-desc-control' ) + .prepend( $readMore ); + + $messageDoc.addClass( 'long compact' ).on( 'mouseenter mouseleave', expand ); + } + + // Enable the collapsible elements, + // used in {{Identical}} on translatewiki.net + $( '.mw-identical-title' ).makeCollapsible(); + } else { + $descEditLink.text( mw.msg( 'tux-editor-add-desc' ) ); + } + + $messageDescViewer.removeClass( 'hide' ); + }, + + /** + * Shows uneditable documentation. + * + * @param {Object} documentation A gettext object as returned by API. + */ + showUneditableDocumentation: function ( documentation ) { + var dir; + + if ( documentation.error ) { + return; + } + + dir = $.uls.data.getDir( documentation.language ); + + this.$editor.find( '.uneditable-documentation' ) + .attr( { + lang: documentation.language, + dir: dir + } ) + .addClass( 'mw-content-' + dir ) + .html( documentation.html ) + .removeClass( 'hide' ); + }, + + /** + * Shows the translations from other languages + * + * @param {Array} translations An inotherlanguages array as returned by the translation helpers API. + */ + showAssistantLanguages: function ( translations ) { + var translateEditor = this; + + if ( translations.error ) { + // Do not proceed if errored/unsupported + return; + } + + $.each( translations, function ( index ) { + var $otherLanguage, langAttr, + translation = translations[ index ]; + + langAttr = { + lang: translation.language, + dir: $.uls.data.getDir( translation.language ) + }; + + $otherLanguage = $( '<div>' ) + .addClass( 'row in-other-language' ) + .append( + $( '<div>' ) + .addClass( 'nine columns suggestiontext' ) + .attr( langAttr ) + .text( translation.value ), + $( '<div>' ) + .addClass( 'three columns language text-right' ) + .attr( langAttr ) + .text( $.uls.data.getAutonym( translation.language ) ) + ); + + translateEditor.suggestionAdder( $otherLanguage, translation.value ); + + translateEditor.$editor.find( '.in-other-languages-title' ) + .removeClass( 'hide' ) + .after( $otherLanguage ); + } ); + }, + + /** + * Shows the translation suggestions from Translation Memory + * + * @param {Array} suggestions A ttmserver array as returned by API. + */ + showTranslationMemory: function ( suggestions ) { + var $heading, $tmSuggestions, $messageList, translationLang, translationDir, + translateEditor = this; + + if ( !suggestions.length ) { + return; + } + + // Container for the suggestions + $tmSuggestions = $( '<div>' ).addClass( 'tm-suggestions' ); + + $heading = this.$editor.find( '.tm-suggestions-title' ); + $heading.after( $tmSuggestions ); + + $messageList = $( '.tux-messagelist' ); + translationLang = $messageList.data( 'targetlangcode' ); + translationDir = $messageList.data( 'targetlangdir' ); + + $.each( suggestions, function ( index, translation ) { + var $translation, + alreadyOnTheList = false; + + if ( translation.local && translation.location === translateEditor.message.title ) { + // Do not add self-suggestions + return true; + } + + // See if it is already listed, and increment use count + $tmSuggestions.find( '.tm-suggestion' ).each( function () { + var $uses, count, + $suggestion = $( this ); + + if ( $suggestion.find( '.suggestiontext ' ).text() === translation.target ) { + // Update the message and data value + $uses = $suggestion.find( '.n-uses' ); + count = $uses.data( 'n' ) + 1; + $uses.data( 'n', count ); + $uses.text( mw.msg( 'tux-editor-n-uses', count ) + ' 〉' ); + + // Halt processing + alreadyOnTheList = true; + return false; + } + } ); + + if ( alreadyOnTheList ) { + // Continue to the next one + return true; + } + + $translation = $( '<div>' ) + .addClass( 'row tm-suggestion' ) + .append( + $( '<div>' ) + .addClass( 'nine columns suggestiontext' ) + .attr( { + lang: translationLang, + dir: translationDir + } ) + .text( translation.target ), + $( '<div>' ) + .addClass( 'three columns quality text-right' ) + .text( mw.msg( 'tux-editor-tm-match', + mw.language.convertNumber( Math.floor( translation.quality * 100 ) ) ) ), + $( '<div>' ) + .addClass( 'row text-right' ) + .append( + $( '<a>' ) + .addClass( 'n-uses' ) + .data( 'n', 1 ) + ) + ); + + translateEditor.suggestionAdder( $translation, translation.target ); + + $tmSuggestions.append( $translation ); + } ); + + // Show the heading only if we actually have suggestions + if ( $tmSuggestions.length ) { + $heading.removeClass( 'hide' ); + } + }, + + /** + * Shows the translation from machine translation systems + * + * @param {Array} suggestions + */ + showMachineTranslations: function ( suggestions ) { + var $mtSuggestions, $messageList, translationLang, translationDir, + translateEditor = this; + + if ( !suggestions.length ) { + return; + } + + $mtSuggestions = this.$editor.find( '.tm-suggestions' ); + + if ( !$mtSuggestions.length ) { + $mtSuggestions = $( '<div>' ).addClass( 'tm-suggestions' ); + } + + this.$editor.find( '.tm-suggestions-title' ) + .removeClass( 'hide' ) + .after( $mtSuggestions ); + + $messageList = $( '.tux-messagelist' ); + translationLang = $messageList.data( 'targetlangcode' ); + translationDir = $messageList.data( 'targetlangdir' ); + + $.each( suggestions, function ( index, translation ) { + var $translation; + + $translation = $( '<div>' ) + .addClass( 'row tm-suggestion' ) + .append( + $( '<div>' ) + .addClass( 'nine columns suggestiontext' ) + .attr( { + lang: translationLang, + dir: translationDir + } ) + .text( translation.target ), + $( '<div>' ) + .addClass( 'three columns text-right service' ) + .text( translation.service ) + ); + + translateEditor.suggestionAdder( $translation, translation.target ); + + $mtSuggestions.append( $translation ); + } ); + }, + + /** + * Makes the $source element clickable and clicking it will replace the + * translation textarea with the given suggestion. + * + * @param {jQuery} $source + * @param {string} suggestion Text to add + */ + suggestionAdder: function ( $source, suggestion ) { + var inserter, + $target = this.$editor.find( '.tux-textarea-translation' ); + + inserter = function () { + var selection; + if ( window.getSelection ) { + selection = window.getSelection().toString(); + } else if ( document.selection && document.selection.type !== 'Control' ) { + selection = document.selection.createRange().text; + } + + if ( !selection ) { + $target.val( suggestion ).focus().trigger( 'input' ); + } + }; + + $source.on( 'click', inserter ); + $source.addClass( 'shortcut-activated' ); + }, + + /** + * Shows the support options for the translator. + * + * @param {Object} support A support object as returned by API. + */ + showSupportOptions: function ( support ) { + // Support URL + if ( support.url ) { + this.$editor.find( '.help a' ).attr( 'href', support.url ); + this.$editor.find( '.help' ).removeClass( 'hide' ); + } + }, + + /** + * Adds buttons for quickly inserting insertables. + * + * @param {Object} insertables A insertables object as returned by API. + */ + addInsertables: function ( insertables ) { + var i, + count = insertables.length, + $sourceMessage = this.$editor.find( '.sourcemessage' ), + $buttonArea = this.$editor.find( '.tux-editor-insert-buttons' ), + $textarea = this.$editor.find( '.tux-textarea-translation' ); + + for ( i = 0; i < count; i++ ) { + // The dir and lang attributes must be set here, + // because the language of the insertables is the language + // of the source message and not of the translation. + // The direction may appear confusing, for example, + // in tvar strings, which would appear with the dollar sign + // on the wrong end. + $( '<button>' ) + .prop( { + lang: $sourceMessage.prop( 'lang' ), + dir: $sourceMessage.prop( 'dir' ) + } ) + .addClass( 'insertable shortcut-activated' ) + .text( insertables[ i ].display ) + .data( 'iid', i ) + .appendTo( $buttonArea ); + } + + $buttonArea.on( 'click', '.insertable', function () { + var data = insertables[ $( this ).data( 'iid' ) ]; + $textarea.textSelection( 'encapsulateSelection', { + pre: data.pre, + post: data.post + } ); + $textarea.focus().trigger( 'input' ); + } ); + + this.resizeInsertables( $textarea ); + }, + + /** + * Loads and shows the translation helpers. + */ + showTranslationHelpers: function () { + // API call to get translation suggestions from other languages + // callback should render suggestions to the editor's info column + var translateEditor = this, + api = new mw.Api(); + + api.get( { + action: 'translationaids', + title: this.message.title + } ).done( function ( result ) { + translateEditor.$editor.find( '.infocolumn .loading' ).remove(); + + if ( !result.helpers ) { + mw.log( 'API did not return any translation helpers.' ); + return false; + } + + translateEditor.showMessageDocumentation( result.helpers.documentation ); + translateEditor.showUneditableDocumentation( result.helpers.gettext ); + translateEditor.showAssistantLanguages( result.helpers.inotherlanguages ); + translateEditor.showTranslationMemory( result.helpers.ttmserver ); + translateEditor.showMachineTranslations( result.helpers.mt ); + translateEditor.showSupportOptions( result.helpers.support ); + translateEditor.addDefinitionDiff( result.helpers.definitiondiff ); + translateEditor.addInsertables( result.helpers.insertables ); + + // Load the possible warnings as soon as possible, do not wait + // for the user to make changes. Otherwise users might try confirming + // translations which fail checks. Confirmation seems to work but + // the message will continue to appear outdated. + if ( translateEditor.message.properties && + translateEditor.message.properties.status === 'fuzzy' + ) { + translateEditor.validateTranslation(); + } + + mw.hook( 'mw.translate.editor.showTranslationHelpers' ).fire( result.helpers, translateEditor.$editor ); + + } ).fail( function ( errorCode, results ) { + mw.log( 'Error loading translation aids', errorCode, results ); + } ); + } + }; + + mw.translate = mw.translate || {}; + + mw.translate = $.extend( mw.translate, { + /** + * Get the documentation edit URL for a title + * + * @param {string} title Message title with namespace + * @return {string} URL for editing the documentation + */ + getDocumentationEditURL: function ( title ) { + return mw.util.getUrl( + title + '/' + mw.config.get( 'wgTranslateDocumentationLanguageCode' ), + { action: 'edit' } + ); + } + } ); + + // Extend the translate editor + mw.translate.editor = mw.translate.editor || {}; + $.extend( mw.translate.editor, translateEditorHelpers ); + +}() ); |