|
| 1 | +/** |
| 2 | + * @OnlyCurrentDoc |
| 3 | + * |
| 4 | + * The above comment directs Apps Script to limit the scope of file |
| 5 | + * access for this add-on. It specifies that this add-on will only |
| 6 | + * attempt to read or modify the files in which the add-on is used, |
| 7 | + * and not all of the user's files. The authorization request message |
| 8 | + * presented to users will reflect this limited scope. |
| 9 | + */ |
| 10 | + |
| 11 | +/** |
| 12 | + * Creates a menu entry in the Google Docs UI when the document is opened. |
| 13 | + * This method is only used by the regular add-on, and is never called by |
| 14 | + * the mobile add-on version. |
| 15 | + * |
| 16 | + * @param {object} e The event parameter for a simple onOpen trigger. To |
| 17 | + * determine which authorization mode (ScriptApp.AuthMode) the trigger is |
| 18 | + * running in, inspect e.authMode. |
| 19 | + */ |
| 20 | +function onOpen(e) { |
| 21 | + DocumentApp.getUi().createAddonMenu() |
| 22 | + .addItem('Start', 'showSidebar') |
| 23 | + .addToUi(); |
| 24 | +} |
| 25 | + |
| 26 | +/** |
| 27 | + * Runs when the add-on is installed. |
| 28 | + * This method is only used by the regular add-on, and is never called by |
| 29 | + * the mobile add-on version. |
| 30 | + * |
| 31 | + * @param {object} e The event parameter for a simple onInstall trigger. To |
| 32 | + * determine which authorization mode (ScriptApp.AuthMode) the trigger is |
| 33 | + * running in, inspect e.authMode. (In practice, onInstall triggers always |
| 34 | + * run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or |
| 35 | + * AuthMode.NONE.) |
| 36 | + */ |
| 37 | +function onInstall(e) { |
| 38 | + onOpen(e); |
| 39 | +} |
| 40 | + |
| 41 | +/** |
| 42 | + * Opens a sidebar in the document containing the add-on's user interface. |
| 43 | + * This method is only used by the regular add-on, and is never called by |
| 44 | + * the mobile add-on version. |
| 45 | + */ |
| 46 | +function showSidebar() { |
| 47 | + var ui = HtmlService.createHtmlOutputFromFile('Sidebar') |
| 48 | + .setTitle('Translate'); |
| 49 | + DocumentApp.getUi().showSidebar(ui); |
| 50 | +} |
| 51 | + |
| 52 | +/** |
| 53 | + * Gets the text the user has selected. If there is no selection, |
| 54 | + * this function displays an error message. |
| 55 | + * |
| 56 | + * @return {Array.<string>} The selected text. |
| 57 | + */ |
| 58 | +function getSelectedText() { |
| 59 | + var selection = DocumentApp.getActiveDocument().getSelection(); |
| 60 | + if (selection) { |
| 61 | + var text = []; |
| 62 | + var elements = selection.getSelectedElements(); |
| 63 | + for (var i = 0; i < elements.length; i++) { |
| 64 | + if (elements[i].isPartial()) { |
| 65 | + var element = elements[i].getElement().asText(); |
| 66 | + var startIndex = elements[i].getStartOffset(); |
| 67 | + var endIndex = elements[i].getEndOffsetInclusive(); |
| 68 | + |
| 69 | + text.push(element.getText().substring(startIndex, endIndex + 1)); |
| 70 | + } else { |
| 71 | + var element = elements[i].getElement(); |
| 72 | + // Only translate elements that can be edited as text; skip images and |
| 73 | + // other non-text elements. |
| 74 | + if (element.editAsText) { |
| 75 | + var elementText = element.asText().getText(); |
| 76 | + // This check is necessary to exclude images, which return a blank |
| 77 | + // text element. |
| 78 | + if (elementText != '') { |
| 79 | + text.push(elementText); |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | + if (text.length == 0) { |
| 85 | + throw 'Please select some text.'; |
| 86 | + } |
| 87 | + return text; |
| 88 | + } else { |
| 89 | + throw 'Please select some text.'; |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +/** |
| 94 | + * Gets the stored user preferences for the origin and destination languages, |
| 95 | + * if they exist. |
| 96 | + * This method is only used by the regular add-on, and is never called by |
| 97 | + * the mobile add-on version. |
| 98 | + * |
| 99 | + * @return {Object} The user's origin and destination language preferences, if |
| 100 | + * they exist. |
| 101 | + */ |
| 102 | +function getPreferences() { |
| 103 | + var userProperties = PropertiesService.getUserProperties(); |
| 104 | + var languagePrefs = { |
| 105 | + originLang: userProperties.getProperty('originLang'), |
| 106 | + destLang: userProperties.getProperty('destLang') |
| 107 | + }; |
| 108 | + return languagePrefs; |
| 109 | +} |
| 110 | + |
| 111 | +/** |
| 112 | + * Gets the user-selected text and translates it from the origin language to the |
| 113 | + * destination language. The languages are notated by their two-letter short |
| 114 | + * form. For example, English is 'en', and Spanish is 'es'. The origin language |
| 115 | + * may be specified as an empty string to indicate that Google Translate should |
| 116 | + * auto-detect the language. |
| 117 | + * |
| 118 | + * @param {string} origin The two-letter short form for the origin language. |
| 119 | + * @param {string} dest The two-letter short form for the destination language. |
| 120 | + * @param {boolean} savePrefs Whether to save the origin and destination |
| 121 | + * language preferences. |
| 122 | + * @return {Object} Object containing the original text and the result of the |
| 123 | + * translation. |
| 124 | + */ |
| 125 | +function getTextAndTranslation(origin, dest, savePrefs) { |
| 126 | + var result = {}; |
| 127 | + var text = getSelectedText(); |
| 128 | + result['text'] = text.join('\n'); |
| 129 | + |
| 130 | + if (savePrefs == true) { |
| 131 | + var userProperties = PropertiesService.getUserProperties(); |
| 132 | + userProperties.setProperty('originLang', origin); |
| 133 | + userProperties.setProperty('destLang', dest); |
| 134 | + } |
| 135 | + |
| 136 | + result['translation'] = translateText(result['text'], origin, dest); |
| 137 | + |
| 138 | + return result; |
| 139 | +} |
| 140 | + |
| 141 | +/** |
| 142 | + * Replaces the text of the current selection with the provided text, or |
| 143 | + * inserts text at the current cursor location. (There will always be either |
| 144 | + * a selection or a cursor.) If multiple elements are selected, only inserts the |
| 145 | + * translated text in the first element that can contain text and removes the |
| 146 | + * other elements. |
| 147 | + * |
| 148 | + * @param {string} newText The text with which to replace the current selection. |
| 149 | + */ |
| 150 | +function insertText(newText) { |
| 151 | + var selection = DocumentApp.getActiveDocument().getSelection(); |
| 152 | + if (selection) { |
| 153 | + var replaced = false; |
| 154 | + var elements = selection.getSelectedElements(); |
| 155 | + if (elements.length == 1 && |
| 156 | + elements[0].getElement().getType() == |
| 157 | + DocumentApp.ElementType.INLINE_IMAGE) { |
| 158 | + throw "Can't insert text into an image."; |
| 159 | + } |
| 160 | + for (var i = 0; i < elements.length; i++) { |
| 161 | + if (elements[i].isPartial()) { |
| 162 | + var element = elements[i].getElement().asText(); |
| 163 | + var startIndex = elements[i].getStartOffset(); |
| 164 | + var endIndex = elements[i].getEndOffsetInclusive(); |
| 165 | + |
| 166 | + var remainingText = element.getText().substring(endIndex + 1); |
| 167 | + element.deleteText(startIndex, endIndex); |
| 168 | + if (!replaced) { |
| 169 | + element.insertText(startIndex, newText); |
| 170 | + replaced = true; |
| 171 | + } else { |
| 172 | + // This block handles a selection that ends with a partial element. We |
| 173 | + // want to copy this partial text to the previous element so we don't |
| 174 | + // have a line-break before the last partial. |
| 175 | + var parent = element.getParent(); |
| 176 | + parent.getPreviousSibling().asText().appendText(remainingText); |
| 177 | + // We cannot remove the last paragraph of a doc. If this is the case, |
| 178 | + // just remove the text within the last paragraph instead. |
| 179 | + if (parent.getNextSibling()) { |
| 180 | + parent.removeFromParent(); |
| 181 | + } else { |
| 182 | + element.removeFromParent(); |
| 183 | + } |
| 184 | + } |
| 185 | + } else { |
| 186 | + var element = elements[i].getElement(); |
| 187 | + if (!replaced && element.editAsText) { |
| 188 | + // Only translate elements that can be edited as text, removing other |
| 189 | + // elements. |
| 190 | + element.clear(); |
| 191 | + element.asText().setText(newText); |
| 192 | + replaced = true; |
| 193 | + } else { |
| 194 | + // We cannot remove the last paragraph of a doc. If this is the case, |
| 195 | + // just clear the element. |
| 196 | + if (element.getNextSibling()) { |
| 197 | + element.removeFromParent(); |
| 198 | + } else { |
| 199 | + element.clear(); |
| 200 | + } |
| 201 | + } |
| 202 | + } |
| 203 | + } |
| 204 | + } else { |
| 205 | + var cursor = DocumentApp.getActiveDocument().getCursor(); |
| 206 | + var surroundingText = cursor.getSurroundingText().getText(); |
| 207 | + var surroundingTextOffset = cursor.getSurroundingTextOffset(); |
| 208 | + |
| 209 | + // If the cursor follows or preceds a non-space character, insert a space |
| 210 | + // between the character and the translation. Otherwise, just insert the |
| 211 | + // translation. |
| 212 | + if (surroundingTextOffset > 0) { |
| 213 | + if (surroundingText.charAt(surroundingTextOffset - 1) != ' ') { |
| 214 | + newText = ' ' + newText; |
| 215 | + } |
| 216 | + } |
| 217 | + if (surroundingTextOffset < surroundingText.length) { |
| 218 | + if (surroundingText.charAt(surroundingTextOffset) != ' ') { |
| 219 | + newText += ' '; |
| 220 | + } |
| 221 | + } |
| 222 | + cursor.insertText(newText); |
| 223 | + } |
| 224 | +} |
| 225 | + |
| 226 | + |
| 227 | +/** |
| 228 | + * Given text, translate it from the origin language to the destination |
| 229 | + * language. The languages are notated by their two-letter short form. For |
| 230 | + * example, English is 'en', and Spanish is 'es'. The origin language may be |
| 231 | + * specified as an empty string to indicate that Google Translate should |
| 232 | + * auto-detect the language. |
| 233 | + * |
| 234 | + * @param {string} text text to translate. |
| 235 | + * @param {string} origin The two-letter short form for the origin language. |
| 236 | + * @param {string} dest The two-letter short form for the destination language. |
| 237 | + * @return {string} The result of the translation, or the original text if |
| 238 | + * origin and dest languages are the same. |
| 239 | + */ |
| 240 | +function translateText(text, origin, dest) { |
| 241 | + if (origin === dest) { |
| 242 | + return text; |
| 243 | + } |
| 244 | + return LanguageApp.translate(text, origin, dest); |
| 245 | +} |
0 commit comments