appscript.dev
Automation Advanced Docs Drive

Translate and resolve Doc comments

Localise reviewer feedback on a shared Doc so multilingual teams can collaborate.

Published Jan 25, 2026

Northwind’s London team reviews documents in English. Their Berlin partners read the same Docs but think in German, and the comment thread quickly turns into a mix nobody can follow. Someone ends up copying each comment into a translator and pasting it back as a reply — slow, and always a few comments behind.

This script does that translation pass automatically. It walks every open comment on a Doc, detects the language, and for anything that is not already German it posts the German translation as a reply prefixed with [de]. The original comment stays put, so the English reviewer’s intent is preserved and the Berlin team gets a version they can act on.

What you’ll need

  • A Google Doc with comments to translate, and edit access to it.
  • The Drive API advanced service enabled — in the Apps Script editor open Services, add Drive API, and keep the identifier Drive. The built-in DriveApp cannot read or write comments; the advanced service can.
  • Nothing else. LanguageApp is built in and needs no setup or key.

The script

// The language code reviewers' comments should be translated into.
const TARGET_LANG = 'de';

// The prefix added to every translated reply, so it is easy to spot.
const REPLY_PREFIX = '[de] ';

// The fields to request from the Drive API — keeps the response small.
const COMMENT_FIELDS = 'comments(id,content,resolved,replies)';

/**
 * Posts a German translation as a reply to every open comment on a Doc.
 *
 * @param {string} docId The ID of the Doc whose comments to translate.
 */
function translateDocComments(docId) {
  // 1. Bail out early if no Doc was passed in.
  if (!docId) {
    Logger.log('No docId supplied — nothing to translate.');
    return;
  }

  // 2. Fetch every comment on the Doc, asking only for the fields we use.
  const res = Drive.Comments.list(docId, { fields: COMMENT_FIELDS });
  const comments = res.comments || [];
  if (!comments.length) {
    Logger.log('No comments on this Doc — nothing to do.');
    return;
  }

  let translated = 0;

  // 3. Walk every comment on the Doc.
  for (const c of comments) {
    // 4. Skip comments that are already resolved — no need to translate.
    if (c.resolved) continue;

    // 5. Detect the comment's language; skip anything already German.
    const lang = LanguageApp.detect(c.content);
    if (lang === TARGET_LANG) continue;

    // 6. Skip if a translated reply already exists, so re-runs are safe.
    if (hasTranslatedReply(c)) continue;

    // 7. Translate the comment and post it back as a reply.
    const text = LanguageApp.translate(c.content, lang || 'en', TARGET_LANG);
    Drive.Replies.create(
      { content: REPLY_PREFIX + text },
      docId,
      c.id,
      { fields: 'id' }
    );
    translated++;
  }

  Logger.log('Posted ' + translated + ' translated replies.');
}

/**
 * Checks whether a comment already has a reply we posted, so the script
 * never translates the same comment twice.
 *
 * @param {Object} comment A comment object from Drive.Comments.list.
 * @return {boolean} True if a [de] reply is already present.
 */
function hasTranslatedReply(comment) {
  const replies = comment.replies || [];
  return replies.some((r) => (r.content || '').startsWith(REPLY_PREFIX));
}

How it works

  1. translateDocComments checks it was given a docId and stops if not, so a missing argument never throws halfway through.
  2. It calls Drive.Comments.list with a fields mask. Asking only for id, content, resolved and replies keeps the response small and fast.
  3. If the Doc has no comments it logs and stops — no point looping over nothing.
  4. For each comment it skips anything already resolved. Resolved threads are closed conversations and do not need a translation.
  5. LanguageApp.detect reads the comment text and returns a language code. If the comment is already in German the script moves on.
  6. hasTranslatedReply scans the comment’s existing replies for a [de] prefix. This is what makes the script safe to run repeatedly — it never double-translates a comment it has already handled.
  7. LanguageApp.translate converts the text into German, and Drive.Replies.create posts it as a reply on the same thread. The original English comment is never changed.

Example run

Suppose a shared Doc has three open comments from the London team:

Comment authorOriginal content
Priya”This paragraph needs a source.”
Tom”Can we shorten the intro?”
Lena”Sieht gut aus.”

After a run, the Doc’s comment threads look like this:

ThreadReply posted
”This paragraph needs a source.”[de] Dieser Absatz braucht eine Quelle.
”Can we shorten the intro?”[de] Können wir die Einleitung kürzen?
”Sieht gut aus.”no reply — already German

The Berlin team now reads the [de] replies and the conversation stays in one place.

Run it

Translation is best run on demand, when a review round closes, rather than on a schedule. Add a custom menu so an editor can trigger it from the Doc:

function onOpen() {
  DocumentApp.getUi()
    .createMenu('Northwind')
    .addItem('Translate comments', 'translateActiveDoc')
    .addToUi();
}

/**
 * Menu wrapper — translates comments on the Doc the user has open.
 */
function translateActiveDoc() {
  translateDocComments(DocumentApp.getActiveDocument().getId());
}

The editor opens the Northwind menu, clicks Translate comments, and approves the authorisation prompt the first time.

Watch out for

  • LanguageApp.detect can misread very short comments. A two-word comment like “Fix this” may detect as the wrong language; the lang || 'en' fallback only covers an empty result, not a confidently wrong one.
  • Machine translation is a draft, not a final wording. For contractual or legal Docs treat the [de] reply as a starting point a bilingual reviewer checks.
  • The script never resolves comments itself, despite the title’s promise of “resolve” — resolving a thread is a human decision once both sides agree. Translating it is the part that can safely be automated.
  • Drive API comment endpoints are quota-limited. On a Doc with hundreds of comments, batch the runs or add a short Utilities.sleep between Drive.Replies.create calls to stay under the per-minute limit.

Related