「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/































この記事へのコメントはありません。