Merge branch 'master' of github.com:apache/incubator-echarts-bot
diff --git a/index.js b/index.js
index e8b98c7..0540f08 100644
--- a/index.js
+++ b/index.js
@@ -2,29 +2,36 @@
 const text = require('./src/text');
 const { isCommitter } = require('./src/coreCommitters');
 const logger = require('./src/logger');
-const translator = require('./src/translator');
-const { replaceAll, removeCodeAndComment } = require('./src/util');
+const { replaceAll } = require('./src/util');
 
 module.exports = (app) => {
     app.on(['issues.opened'], async context => {
         const issue = new Issue(context);
 
+        await issue.init();
+
         // Ignore comment because it will commented when adding invalid label
-        const comment = issue.response === text.NOT_USING_TEMPLATE
+        const comment = !issue.response || issue.response === text.NOT_USING_TEMPLATE
             ? Promise.resolve()
-            : commentIssue(context, issue.response, issue.response === text.ISSUE_CREATED)
+            : commentIssue(context, issue.response);
 
         const addLabels = issue.addLabels.length
-            ? context.octokit.issues.addLabels(context.issue({
-                labels: issue.addLabels
-            }))
+            ? context.octokit.issues.addLabels(
+                context.issue({
+                    labels: issue.addLabels
+                })
+              )
             : Promise.resolve();
 
         const removeLabel = issue.removeLabel
             ? getRemoveLabel(issue.removeLabel)
             : Promise.resolve();
 
-        return Promise.all([comment, addLabels, removeLabel]);
+        const translate = issue.response === text.ISSUE_CREATED
+            ? Promise.resolve()
+            : translateIssue(context, issue);
+
+        return Promise.all([comment, addLabels, removeLabel, translate]);
     });
 
     app.on('issues.labeled', async context => {
@@ -84,9 +91,11 @@
         else if (isCommenterAuthor) {
             // New comment from issue author
             removeLabel = getRemoveLabel(context, 'waiting-for: author');
-            addLabel = context.octokit.issues.addLabels(context.issue({
-                labels: ['waiting-for: community']
-            }));
+            addLabel = context.octokit.issues.addLabels(
+                context.issue({
+                    labels: ['waiting-for: community']
+                })
+            );
         }
         return Promise.all([removeLabel, addLabel]);
     });
@@ -110,13 +119,17 @@
             commentText += '\n\n' + text.PR_AWAITING_DOC;
         }
 
-        const comment = context.octokit.issues.createComment(context.issue({
-            body: commentText
-        }));
+        const comment = context.octokit.issues.createComment(
+            context.issue({
+                body: commentText
+            })
+        );
 
-        const addLabel = context.octokit.issues.addLabels(context.issue({
-            labels: labelList
-        }));
+        const addLabel = context.octokit.issues.addLabels(
+            context.issue({
+                labels: labelList
+            })
+        );
 
         return Promise.all([comment, addLabel]);
     });
@@ -124,9 +137,11 @@
     app.on(['pull_request.edited'], async context => {
         const content = context.payload.pull_request.body;
         if (content && content.indexOf('[x] The API has been changed.') > -1) {
-            return context.octokit.issues.addLabels(context.issue({
-                labels: ['PR: awaiting doc']
-            }));
+            return context.octokit.issues.addLabels(
+                context.issue({
+                    labels: ['PR: awaiting doc']
+                })
+            );
         }
         else {
             return getRemoveLabel(context, 'PR: awaiting doc');
@@ -134,9 +149,11 @@
     });
 
     app.on(['pull_request.synchronize'], async context => {
-        const addLabel = context.octokit.issues.addLabels(context.issue({
-            labels: ['PR: awaiting review']
-        }));
+        const addLabel = context.octokit.issues.addLabels(
+            context.issue({
+                labels: ['PR: awaiting review']
+            })
+        );
         const removeLabel = getRemoveLabel(context, 'PR: revision needed');
         return Promise.all([addLabel, removeLabel]);
     });
@@ -148,9 +165,11 @@
         ];
         const isMerged = context.payload['pull_request'].merged;
         if (isMerged) {
-            const comment = context.octokit.issues.createComment(context.issue({
-                body: text.PR_MERGED
-            }));
+            const comment = context.octokit.issues.createComment(
+                context.issue({
+                    body: text.PR_MERGED
+                })
+            );
             actions.push(comment);
         }
         return Promise.all(actions);
@@ -160,9 +179,11 @@
         if (context.payload.review.state === 'changes_requested'
             && isCommitter(context.payload.review.author_association, context.payload.review.user.login)
         ) {
-            const addLabel = context.octokit.issues.addLabels(context.issue({
-                labels: ['PR: revision needed']
-            }));
+            const addLabel = context.octokit.issues.addLabels(
+                context.issue({
+                    labels: ['PR: revision needed']
+                })
+            );
 
             const removeLabel = getRemoveLabel(context, 'PR: awaiting review');
             return Promise.all([addLabel, removeLabel]);
@@ -189,69 +210,51 @@
 }
 
 function closeIssue(context) {
-    const closeIssue = context.octokit.issues.update(context.issue({
-        state: 'closed'
-    }));
-    return closeIssue;
+    // close issue
+    return context.octokit.issues.update(
+        context.issue({
+            state: 'closed'
+        })
+    );
 }
 
-async function commentIssue(context, commentText, needTranslate) {
+function commentIssue(context, commentText) {
     // create comment
-    await context.octokit.issues.createComment(context.issue({
-        body: commentText
-    }));
+    return context.octokit.issues.createComment(
+        context.issue({
+            body: commentText
+        })
+    );
+}
 
-    logger.info('issue needs translation: ' + needTranslate);
+async function translateIssue (context, createdIssue) {
+    if (!createdIssue) {
+        return;
+    }
+
+    const {
+        title, body,
+        translatedTitle, translatedBody
+    } = createdIssue;
+
+    const titleNeedsTranslation = translatedTitle && translatedTitle[0] !== title;
+    const bodyNeedsTranslation = translatedBody && translatedBody[0] !== body;
+    const needsTranslation = titleNeedsTranslation || bodyNeedsTranslation;
+
+    logger.info('issue needs translation: ' + needsTranslation);
 
     // translate the issue if needed
-    if (needTranslate) {
-        const startTime = Date.now();
-
-        const { title, body } = context.payload.issue;
-        const filteredTitle = removeCodeAndComment(title);
-        const filteredBody = removeCodeAndComment(body);
-
-        let isEnTitle = translator.detectEnglish(filteredTitle);
-        let isEnBody = translator.detectEnglish(filteredBody);
-
-        let translatedTitle;
-        let translatedBody;
-
-        // if the franc has detected it's English, so no need to translate it.
-        if (!isEnTitle) {
-            const res = await translator.translate(title);
-            if (res) {
-                // determine if it's English according to the detected language by Google Translate
-                isEnTitle = res.lang === 'en';
-                translatedTitle = !isEnTitle && res.translated;
-            }
-        }
-        if (!isEnBody) {
-            const res = await translator.translate(body);
-            if (res) {
-                isEnBody = res.lang === 'en';
-                translatedBody = !isEnBody && res.translated;
-            }
-        }
-
-        if ((!isEnTitle || !isEnBody)
-            && (
-                (translatedTitle && translatedTitle !== title)
-                || (translatedBody && translatedBody !== body)
-            )
-        ) {
-            const translateTip = replaceAll(
-                text.ISSUE_COMMENT_TRANSLATE_TIP,
-                'AT_ISSUE_AUTHOR',
-                '@' + context.payload.issue.user.login
-            );
-            const translateComment = `${translateTip}\n<details><summary><b>TRANSLATED</b></summary><br>${!isEnTitle && translatedTitle && title !== translatedTitle ? '\n\n**TITLE**\n\n' + translatedTitle : ''}${!isEnBody && translatedBody && body !== translatedBody ? '\n\n**BODY**\n\n' + translatedBody : ''}\n</details>`;
-            await context.octokit.issues.createComment(
-                context.issue({
-                    body: translateComment
-                })
-            );
-            logger.info(`issue translated - ${Date.now() - startTime}ms`);
-        }
+    if (needsTranslation) {
+        const translateTip = replaceAll(
+            text.ISSUE_COMMENT_TRANSLATE_TIP,
+            'AT_ISSUE_AUTHOR',
+            '@' + createdIssue.issue.user.login
+        );
+        const translateComment = `${translateTip}\n<details><summary><b>TRANSLATED</b></summary><br>${titleNeedsTranslation ? '\n\n**TITLE**\n\n' + translatedTitle[0] : ''}${bodyNeedsTranslation ? '\n\n**BODY**\n\n' + translatedBody[0] : ''}\n</details>`;
+        await context.octokit.issues.createComment(
+            context.issue({
+                body: translateComment
+            })
+        );
     }
 }
diff --git a/src/issue.js b/src/issue.js
index 1365321..136ab75 100644
--- a/src/issue.js
+++ b/src/issue.js
@@ -1,7 +1,6 @@
 const text = require('./text');
 const { isCommitter } = require('./coreCommitters');
-const { removeCodeAndComment } = require('./util')
-const { detectEnglish } = require('./translator')
+const { translate } = require('./translator');
 
 class Issue {
     constructor(context) {
@@ -9,34 +8,63 @@
         this.issue = context.payload.issue;
         this.title = this.issue.title;
         this.body = this.issue.body;
+        // null -> failed to translate -> unknown language
+        // false -> translated -> not in English
+        this.translatedTitle = null;
+        this.translatedBody = null;
         this.issueType = null;
+        this.response = null;
         this.addLabels = [];
         this.removeLabel = null;
+    }
 
+    async init () {
         // if author is committer, do not check if using template
         const isCore = isCommitter(this.issue.author_association, this.issue.user.login);
         if (isCore || this.isUsingTemplate()) {
-            this.init();
-        }
-        else {
+            if (this._contain('Steps to reproduce')) {
+                this.issueType = 'bug';
+            } else if (this._contain('What problem does this feature solve')) {
+                this.issueType = 'new-feature';
+            } else if (!isCore) {
+                this.response = text.NOT_USING_TEMPLATE;
+                return;
+            }
+
+            if (!isCore) {
+                this.addLabels.push('pending');
+                this.addLabels.push('waiting-for: community');
+            }
+
+            this.issueType && this.addLabels.push(this.issueType);
+
+            // translate issue
+            await this._translate();
+
+            // const isInEnglish = this._contain('This issue is in English');
+            const isInEnglish = (!this.translatedTitle && !this.translatedBody)
+              || (!this.title.trim() && !this.translatedBody)
+              || (!this.body.trim() && !this.translatedTitle);
+            if (isInEnglish) {
+                this.addLabels.push('en');
+            }
+
+            isCore || this._computeResponse();
+        } else {
             this.response = text.NOT_USING_TEMPLATE;
             this.addLabels.push('invalid');
         }
     }
 
-    init() {
-        if (this._contain('Steps to reproduce')) {
-            this.issueType = 'bug';
+    async _translate () {
+        let res = await translate(this.title);
+        if (res) {
+            this.translatedTitle = res.lang !== 'en' && [res.translated, res.lang];
         }
-        else if (this._contain('What problem does this feature solve')) {
-            this.issueType = 'new-feature';
+        res = await translate(this.body);
+        if (res) {
+            this.translatedBody = res.lang !== 'en' && [res.translated, res.lang];
         }
-        else {
-            this.response = text.NOT_USING_TEMPLATE;
-            return;
-        }
-
-        this._computeResponse();
     }
 
     isUsingTemplate() {
@@ -54,17 +82,6 @@
                 this.removeLabel = 'waiting-for: help';
                 break;
         }
-
-        this.addLabels.push('waiting-for: community');
-        this.addLabels.push('pending');
-        this.addLabels.push(this.issueType);
-
-        const isInEnglish = this._contain('This issue is in English');
-        if (isInEnglish &&
-            detectEnglish(removeCodeAndComment(this.title)) &&
-            detectEnglish(removeCodeAndComment(this.body))) {
-            this.addLabels.push('en');
-        }
     }
 
     _contain(txt) {