「Google Docs用アドオンストアがオープン、表計算や文書作成に新機能を追加可能に」にあるように、Google ドキュメントの文書とスプレッドシートに機能を追加・拡張する「アドオン」を公開するためのプラットフォーム、「アドオンストア」がオープンしました。
このアドオンは一般の開発者もGoogle Apps Scriptを使って簡単に開発できるとのことなので、早速試してみました。
- Google ドキュメントで文書を新規作成します。
- 「ツール」メニューから「スクリプト エディタ」をクリックします。
- ようこそ画面(welcome screen)が表示されるので「空のプロジェクト」をクリックします。
- 最初に表示されるコードをすべて選択して削除します。
- 下記コードを貼りつけます。
- 「ファイル」メニューの「新規作成」から「HTMLファイル」をクリックします。
- ファイルを作成ダイアログでファイル名を「Sidebar」として、「OK」ボタンをクリックします。
- Sidebar.htmlが表示されるので、コードをすべて選択して削除します。
- 下記コードを貼りつけます。
- 「ファイル」メニューから「すべてを保存」をクリックします。
- プロジェクト名を変更ダイアログでプロジェクト名を「Translate Quickstart」として、「OK」ボタンをクリックします。
- スクリプト エディタを終了し、1.で作成した文書も一度閉じます。
- 12.で閉じた文書を再度開くと「アドオン」メニューに「Translate Quickstart」が追加されていることが確認できます。
- 「アドオン」メニューの「Translate Quickstart」から「Start」をクリックします。
- 承認画面が表示されたら「続行」ボタンをクリックし、続いて表示されるウィンドウで「承認する」ボタンをクリックします。
- Google ドキュメントの画面右側に「Translate」が表示されることが確認できます。
- 文書中の文字列を選択後、Translate上で言語を選び「Translate」ボタンをクリックすると、テキストエリアに翻訳された文字列が表示されます。
- さらに「Insert」ボタンをクリックすることで、テキストエリアの文字列を文書に挿入することができます。
/** * Creates a menu entry in the Google Docs UI when the document is opened. */ function onOpen() { DocumentApp.getUi().createAddonMenu() .addItem('Start', 'showSidebar') .addToUi(); } /** * Runs when the add-on is installed. */ function onInstall() { onOpen(); } /** * Opens a sidebar in the document containing the add-on's user interface. */ function showSidebar() { var ui = HtmlService.createHtmlOutputFromFile('Sidebar') .setTitle('Translate'); DocumentApp.getUi().showSidebar(ui); } /** * Gets the text the user has selected. If there is no selection, * this function displays an error message. * * @return {Array.<string>} The selected text. */ function getSelectedText() { var selection = DocumentApp.getActiveDocument().getSelection(); if (selection) { var text = []; var elements = selection.getSelectedElements(); for (var i = 0; i < elements.length; i++) { if (elements[i].isPartial()) { var element = elements[i].getElement().asText(); var startIndex = elements[i].getStartOffset(); var endIndex = elements[i].getEndOffsetInclusive(); text.push(element.getText().substring(startIndex, endIndex + 1)); } else { var element = elements[i].getElement(); // Only translate elements that can be edited as text; skip images and // other non-text elements. if (element.editAsText) { var elementText = element.asText().getText(); // This check is necessary to exclude images, which return a blank // text element. if (elementText != '') { text.push(elementText); } } } } if (text.length == 0) { throw 'Please select some text.'; } return text; } else { throw 'Please select some text.'; } } /** * Gets the stored user preferences for the origin and destination languages, * if they exist. * * @return {Object} The user's origin and destination language preferences, if * they exist. */ function getPreferences() { var userProperties = PropertiesService.getUserProperties(); var languagePrefs = { originLang: userProperties.getProperty('originLang'), destLang: userProperties.getProperty('destLang') }; return languagePrefs; } /** * Gets the user-selected text and translates it from the origin language to the * destination language. The languages are notated by their two-letter short * form. For example, English is 'en', and Spanish is 'es'. The origin language * may be specified as an empty string to indicate that Google Translate should * auto-detect the language. * * @param {string} origin The two-letter short form for the origin language. * @param {string} dest The two-letter short form for the destination language. * @param {boolean} savePrefs Whether to save the origin and destination * language preferences. * @return {string} The result of the translation. */ function runTranslation(origin, dest, savePrefs) { var text = getSelectedText(); if (savePrefs == true) { var userProperties = PropertiesService.getUserProperties(); userProperties.setProperty('originLang', origin); userProperties.setProperty('destLang', dest); } var translated = []; for (var i = 0; i < text.length; i++) { translated.push(LanguageApp.translate(text[i], origin, dest)); } return translated.join('\n'); } /** * Replaces the text of the current selection with the provided text, or * inserts text at the current cursor location. (There will always be either * a selection or a cursor.) If multiple elements are selected, only inserts the * translated text in the first element that can contain text and removes the * other elements. * * @param {string} newText The text with which to replace the current selection. */ function insertText(newText) { var selection = DocumentApp.getActiveDocument().getSelection(); if (selection) { var replaced = false; var elements = selection.getSelectedElements(); if (elements.length == 1 && elements[0].getElement().getType() == DocumentApp.ElementType.INLINE_IMAGE) { throw "Can't insert text into an image."; } for (var i = 0; i < elements.length; i++) { if (elements[i].isPartial()) { var element = elements[i].getElement().asText(); var startIndex = elements[i].getStartOffset(); var endIndex = elements[i].getEndOffsetInclusive(); var remainingText = element.getText().substring(endIndex + 1); element.deleteText(startIndex, endIndex); if (!replaced) { element.insertText(startIndex, newText); replaced = true; } else { // This block handles a selection that ends with a partial element. We // want to copy this partial text to the previous element so we don't // have a line-break before the last partial. var parent = element.getParent(); parent.getPreviousSibling().asText().appendText(remainingText); // We cannot remove the last paragraph of a doc. If this is the case, // just remove the text within the last paragraph instead. if (parent.getNextSibling()) { parent.removeFromParent(); } else { element.removeFromParent(); } } } else { var element = elements[i].getElement(); if (!replaced && element.editAsText) { // Only translate elements that can be edited as text, removing other // elements. element.clear(); element.asText().setText(newText); replaced = true; } else { // We cannot remove the last paragraph of a doc. If this is the case, // just clear the element. if (element.getNextSibling()) { element.removeFromParent(); } else { element.clear(); } } } } } else { var cursor = DocumentApp.getActiveDocument().getCursor(); var surroundingText = cursor.getSurroundingText().getText(); var surroundingTextOffset = cursor.getSurroundingTextOffset(); // If the cursor follows or preceds a non-space character, insert a space // between the character and the translation. Otherwise, just insert the // translation. if (surroundingTextOffset > 0) { if (surroundingText.charAt(surroundingTextOffset - 1) != ' ') { newText = ' ' + newText; } } if (surroundingTextOffset < surroundingText.length) { if (surroundingText.charAt(surroundingTextOffset) != ' ') { newText += ' '; } } cursor.insertText(newText); } }
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css"> <!-- The CSS package above applies Google styling to buttons and other elements. --> <style> .branding-below { bottom: 56px; top: 0; } .branding-text { left: 7px; position: relative; top: 3px; } .col-contain { overflow: hidden; } .col-one { float: left; width: 50%; } .logo { vertical-align: middle; } .radio-spacer { height: 20px; } .width-100 { width: 100%; } </style> <div class="sidebar branding-below"> <form> <div class="block col-contain"> <div class="col-one"> <b>Selected text</b> <div> <input type="radio" name="origin" id="radio-origin-auto" value="" checked="checked"> <label for="radio-origin-auto">Auto-detect</label> </div> <div> <input type="radio" name="origin" id="radio-origin-en" value="en"> <label for="radio-origin-en">English</label> </div> <div> <input type="radio" name="origin" id="radio-origin-fr" value="fr"> <label for="radio-origin-fr">French</label> </div> <div> <input type="radio" name="origin" id="radio-origin-de" value="de"> <label for="radio-origin-de">German</label> </div> <div> <input type="radio" name="origin" id="radio-origin-ja" value="ja"> <label for="radio-origin-ja">Japanese</label> </div> <div> <input type="radio" name="origin" id="radio-origin-es" value="es"> <label for="radio-origin-es">Spanish</label> </div> </div> <div> <b>Translate into</b> <div class="radio-spacer"> </div> <div> <input type="radio" name="dest" id="radio-dest-en" value="en"> <label for="radio-dest-en">English</label> </div> <div> <input type="radio" name="dest" id="radio-dest-fr" value="fr"> <label for="radio-dest-fr">French</label> </div> <div> <input type="radio" name="dest" id="radio-dest-de" value="de"> <label for="radio-dest-de">German</label> </div> <div> <input type="radio" name="dest" id="radio-dest-ja" value="ja" checked="checked"> <label for="radio-dest-ja">Japanese</label> </div> <div> <input type="radio" name="dest" id="radio-dest-es" value="es"> <label for="radio-dest-es">Spanish</label> </div> </div> </div> <div class="block form-group"> <label for="translated-text"><b>Translation</b></label> <textarea class="width-100" id="translated-text" rows="10"></textarea> </div> <div class="block"> <input type="checkbox" id="save-prefs"> <label for="save-prefs">Use these languages by default</label> </div> <div class="block" id="button-bar"> <button class="blue" id="run-translation">Translate</button> <button id="insert-text">Insert</button> </div> </form> </div> <div class="sidebar bottom"> <img alt="Add-on logo" class="logo" width="27" height="27" src="https://googledrive.com/host/0B0G1UdyJGrY6XzdjQWF4a1JYY1k/translate-logo-small.png"> <span class="gray branding-text">Translate sample by Google</span> </div> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"> </script> <script> /** * On document load, assign click handlers to each button and try to load the * user's origin and destination language preferences if previously set. */ $(function() { $('#run-translation').click(runTranslation); $('#insert-text').click(insertText); google.script.run.withSuccessHandler(loadPreferences) .withFailureHandler(showError).getPreferences(); }); /** * Callback function that populates the origin and destination selection * boxes with user preferences from the server. * * @param {Object} languagePrefs The saved origin and destination languages. */ function loadPreferences(languagePrefs) { $('input:radio[name="origin"]') .filter('[value=' + languagePrefs.originLang + ']') .attr('checked', true); $('input:radio[name="dest"]') .filter('[value=' + languagePrefs.destLang + ']') .attr('checked', true); } /** * Runs a server-side function to translate the user-selected text and update * the sidebar UI with the resulting translation. */ function runTranslation() { this.disabled = true; $('#error').remove(); var origin = $('input[name=origin]:checked').val(); var dest = $('input[name=dest]:checked').val(); var savePrefs = $('#save-prefs').is(':checked'); google.script.run .withSuccessHandler( function(translatedText, element) { $('#translated-text').val(translatedText); element.disabled = false; }) .withFailureHandler( function(msg, element) { showError(msg, $('#button-bar')); element.disabled = false; }) .withUserObject(this) .runTranslation(origin, dest, savePrefs); } /** * Runs a server-side function to insert the translated text into the document * at the user's cursor or selection. */ function insertText() { this.disabled = true; $('#error').remove(); google.script.run .withSuccessHandler( function(returnSuccess, element) { element.disabled = false; }) .withFailureHandler( function(msg, element) { showError(msg, $('#button-bar')); element.disabled = false; }) .withUserObject(this) .insertText($('#translated-text').val()); } /** * Inserts a div that contains an error message after a given element. * * @param msg The error message to display. * @param element The element after which to display the error. */ function showError(msg, element) { var div = $('<div id="error" class="error">' + msg + '</div>'); $(element).after(div); } </script>
以上の手順で、自分でアドオンを作成・実行することができます。
上記手順は、「Quickstart: Add-on for Google Docs」に載っている手順に画像を付け足しただけですが、おおよその概要は掴めるかと思います。
作成したアドオンをストアで公開するには、審査にパスする必要があるようです。詳しくは下記ページをご参照ください。
・Publishing an Add-on – Google Apps Script – Google Developers
https://developers.google.com/apps-script/add-ons/publish
しかしながらこの機能、当ブログで取り扱っている、Microsoft Officeの「Office 用アプリ」にそっくりですね!
今後も要注目の機能です。
■ 関連Webページ
・Google Developers Blog: Building Sheets and Docs Add-ons
http://googledevelopers.blogspot.jp/2014/03/building-sheets-and-docs-add-ons.html
・Google Apps Script – Google Developers
https://developers.google.com/apps-script/add-ons/
この記事へのコメントはありません。