LoginSignup
1
4

More than 5 years have passed since last update.

Google Apps Script クイックスタート:選択したテキストを他の言語に変換するアドオンを5分で作成する

Posted at

同じ内容で画面キャプチャ付きの記事はこちらにあります。

Google翻訳を利用して選択したテキストを他の言語に変換するGoogle Docsのアドオンを作成します。

上図のようにサイドバーとしてテキストを翻訳する機能を追加します。

準備

1.
新しいGoogleドキュメントを作成します。

2.
新しい文書の中から、メニュー項目の[ツール]> [スクリプトエディタ]を選択します。ようこそ画面が表示されたら、[空のプロジェクト]をクリックします。

3.
スクリプトエディタでコードを削除し、下のコードに貼り付けます。

/**
 * @OnlyCurrentDoc
 *
 */

/**
 * ドキュメントを開いたときに、GoogleドキュメントのUIにメニューを作成します。
 * このメソッドは通常のアドオンでのみ使用され、モバイルでは呼び出されません。
 *
 * @param {object} e 単純な onOpen トリガーのイベントパラメーターです。
 *     トリガーが実行されている認証モード(ScriptApp.AuthMode)を確認するには、
 *     e.authModeを調べます。
 */
function onOpen(e) {
  DocumentApp.getUi().createAddonMenu()
      .addItem('Start', 'showSidebar')
      .addToUi();
}

/**
 * アドオンがインストールされているときに実行されます。
 * モバイルでは動きません。
 *
 * @param {object} e 単純なonInstallトリガのイベントパラメータです。
 *     トリガーが実行されている認証モード(ScriptApp.AuthMode)を
 *     確認するには、e.authModeを調べます。(実際には、onInstall
 *     トリガーは常にAuthMode.FULLで実行されますが、onOpenトリガーは
 *     AuthMode.LIMITEDまたはAuthMode.NONEです。)
 */
function onInstall(e) {
  onOpen(e);
}

/**
 * サイドバーを開きます。
 * モバイルでは動きません。
 */
function showSidebar() {
  var ui = HtmlService.createHtmlOutputFromFile('Sidebar')
      .setTitle('Translate');
  DocumentApp.getUi().showSidebar(ui);
}

/**
 * ユーザーが選択したテキストを取得します。
 * 選択がない場合は、エラーメッセージを表示します。
 *
 * @return {Array.} 選択されたテキスト
 */
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();
        // テキストとして編集できる要素のみを翻訳します。
        // 画像やその他のテキスト以外の要素をスキップします。
        if (element.editAsText) {
          var elementText = element.asText().getText();
          // このチェックは、画像を除外して空のテキスト要素を返すために必要です。
          if (elementText != '') {
            text.push(elementText);
          }
        }
      }
    }
    if (text.length == 0) {
      throw 'Please select some text.';
    }
    return text;
  } else {
    throw 'Please select some text.';
  }
}

/**
 * 設定されているオリジナルの言語と目的の言語(翻訳後)を取得します。
 * モバイルでは動きません。
 *
 * @return {Object} オリジナルの言語と目的の言語
 */
function getPreferences() {
  var userProperties = PropertiesService.getUserProperties();
  var languagePrefs = {
    originLang: userProperties.getProperty('originLang'),
    destLang: userProperties.getProperty('destLang')
  };
  return languagePrefs;
}

/**
 * ユーザーが選択したテキストを取得し、オリジナルの言語から目的の言語
 * (翻訳後)に変換します。言語は2文字の短い形式で表記されています。
 * たとえば、英語は「en」、スペイン語は「es」です。
 * オリジナルの言語は、Google Translateが自動的にその言語を検出する
 * ことを示す空の文字列として指定することができます。
 *
 * @param {string} origin オリジナルの言語の2文字の短縮形
 * @param {string} dest 目的の言語の2文字の短縮形
 * @param {boolean} savePrefs オリジナルの言語と目的の言語を保存するかどうか
 * @return {Object} 元のテキストと翻訳後のテキストを含むオブジェクト
 */
function getTextAndTranslation(origin, dest, savePrefs) {
  var result = {};
  var text = getSelectedText();
  result['text'] = text.join('\n');

  if (savePrefs == true) {
    var userProperties = PropertiesService.getUserProperties();
    userProperties.setProperty('originLang', origin);
    userProperties.setProperty('destLang', dest);
  }

  result['translation'] = translateText(result['text'], origin, dest);

  return result;
}

/**
 * 現在の選択範囲のテキストを指定されたテキストに置き換えるか、
 * 現在のカーソル位置にテキストを挿入します。
 * 複数の要素が選択されている場合は、テキストを含むことができる
 * 最初の要素に翻訳されたテキストを挿入し、他の要素を削除します。
 *
 * @param {string} newText 現在の選択を置き換えるテキスト
 */
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 {
          // このブロックは、部分的な要素で終わる選択を処理します。
          // この部分テキストを前の要素にコピーして、最後の部分的な
          // 部分の前に改行がないようにします。
          var parent = element.getParent();
          parent.getPreviousSibling().asText().appendText(remainingText);
          // ドキュメントの最後の段落は削除できません。
          // この場合は、代わりに最後の段落内のテキストを
          // 削除してください。
          if (parent.getNextSibling()) {
            parent.removeFromParent();
          } else {
            element.removeFromParent();
          }
        }
      } else {
        var element = elements[i].getElement();
        if (!replaced && element.editAsText) {
          // テキストとして編集できる要素のみを翻訳し、
          // 他の要素は削除しないでください。
          element.clear();
          element.asText().setText(newText);
          replaced = true;
        } else {
          // ドキュメントの最後の段落は削除できません。
          // このような場合は、要素をクリアしてください。
          if (element.getNextSibling()) {
            element.removeFromParent();
          } else {
            element.clear();
          }
        }
      }
    }
  } else {
    var cursor = DocumentApp.getActiveDocument().getCursor();
    var surroundingText = cursor.getSurroundingText().getText();
    var surroundingTextOffset = cursor.getSurroundingTextOffset();

    // カーソルがスペース以外の文字の前または後にある場合は、
    // 文字と翻訳の間にスペースを挿入します。
    // それ以外の場合は、翻訳を挿入するだけです。
    if (surroundingTextOffset > 0) {
      if (surroundingText.charAt(surroundingTextOffset - 1) != ' ') {
        newText = ' ' + newText;
      }
    }
    if (surroundingTextOffset < surroundingText.length) {
      if (surroundingText.charAt(surroundingTextOffset) != ' ') {
        newText += ' ';
      }
    }
    cursor.insertText(newText);
  }
}


/**
 * Google翻訳を使って翻訳をします。
 * 言語は2文字の短い形式で表記されています。
 * たとえば、英語は「en」、スペイン語は「es」です。
 * オリジナル言語は、Google Translateが自動的にその言語を検出することを示す
 * 空の文字列として指定することができます。
 *
 * @param {string} text 翻訳するテキスト
 * @param {string} origin オリジナルの言語の2文字の短縮形
 * @param {string} dest 目的の言語の2文字の短縮形
 * @return {string} 翻訳の結果
 */
function translateText(text, origin, dest) {
  if (origin === dest) {
    return text;
  }
  return LanguageApp.translate(text, origin, dest);
}

4.
メニューから[ファイル] > [新規作成] > [HTMLファイル] 選択して新しいファイルを作成します。ファイルに「Sidebar」という名前を付けます。

5.
新しいエディタタブでコードを削除し、下のコードに貼り付けます。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.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>
  </head>
  <body>
    <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" src="https://www.gstatic.com/images/branding/product/1x/translate_48dp.png" width="27" height="27">
      <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>
      /**
       * ドキュメントの読み込み時に、各ボタンにクリックハンドラを割り当てます。
       * 設定されている場合は、オリジナルの言語と目的の言語(翻訳後)の
       * 設定を読み込みます。
       */
      $(function() {
        $('#run-translation').click(runTranslation);
        $('#insert-text').click(insertText);
        google.script.run.withSuccessHandler(loadPreferences)
            .withFailureHandler(showError).getPreferences();
      });

      /**
       * サーバーから渡されたオリジナルの言語と目的の言語を設定するための
       * コールバック関数。
       *
       * @param {Object} languagePrefs 保存されたオリジナルの言語と目的の言語
       */
      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);
      }

      /**
       * サーバーサイドで翻訳を実行して、サイドバーの結果を更新します。
       */
      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(textAndTranslation, element) {
                $('#translated-text').val(textAndTranslation.translation);
                element.disabled = false;
              })
            .withFailureHandler(
              function(msg, element) {
                showError(msg, $('#button-bar'));
                element.disabled = false;
              })
            .withUserObject(this)
            .getTextAndTranslation(origin, dest, savePrefs);
      }

      /**
       * サーバー側の機能を実行して、翻訳されたテキストをユーザーのカーソルに
       * 挿入または選択したテキストを置き換えます。
       */
      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());
      }

      /**
       * 指定された要素(element)にエラーメッセージを含むdivタグを挿入します。
       *
       * @param msg 表示するエラーメッセージ
       * @param element エラーを表示する要素
       */
      function showError(msg, element) {
        var div = $('<div id="error" class="error">' + msg + '</div>');
        $(element).after(div);
      }
    </script>
  </body>
</html>
  1. メニューの [ファイル] > [すべてを保存] を選択します。新しいプロジェクト名を「Translate Quickstart」とし、「OK」をクリックします(プロジェクトの名前は、承認ダイアログを含むいくつかの場所のエンドユーザーに表示されます)。

実行

1.
ドキュメントに戻ってページをリロードしてください。アドオンが読み込まれるまで数秒待ちます。

2.
メニュー [アドオン] > [Translate Quickstart] > [Start] を選択します。(スクリプトに別の名前を選択した場合は、代わりにその名前が表示されます)

3.
承認が必要となるので[継続]ボタンを選択してください。

4.
[許可]ボタンを選択してください。

5.
サイドバーが表示されます。
ドキュメントにテキストを入力し、それを選択して青の翻訳ボタンをクリックします。ドキュメント内のテキストを置き換えるには、[Insert]ボタンをクリックします。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4