feat: tweak the type in issue title automatically
diff --git a/index.js b/index.js
index 731f900..1c83430 100644
--- a/index.js
+++ b/index.js
@@ -35,11 +35,19 @@
         const invalid = issue.addLabels.includes(labelText.INVALID)
             || issue.addLabels.includes(labelText.MISSING_TITLE);
 
-        // translate finally if valid
-        return invalid || translateIssue(context, issue);
+        if (!invalid) {
+            // update title
+            issue.isTitleChanged && await updateIssueTitle(context, issue.title);
+            // translate finally if valid
+            await translateIssue(context, issue);
+        }
     });
 
     app.on(['issues.edited'], async context => {
+        if (context.payload.sender.type === 'Bot') {
+            logger.info('skip to handle current `issues.edited` event as it is from bot');
+            return;
+        }
         const ctxIssue = context.payload.issue;
         const labels = ctxIssue.labels;
         if (labels && labels.findIndex(label => label.name === labelText.MISSING_TITLE) > -1) {
@@ -55,8 +63,10 @@
                     await addLabels(context, issue.addLabels);
                     // reopen issue
                     await openIssue(context);
+                    // update title
+                    issue.isTitleChanged && await updateIssueTitle(context, issue.title);
                     // translate
-                    translateIssue(context, issue);
+                    await translateIssue(context, issue);
                 }
             }
         }
@@ -389,6 +399,18 @@
 
 /**
  * @param {Context} context
+ * @param {string} title
+ */
+function updateIssueTitle(context, title) {
+    return context.octokit.issues.update(
+        context.issue({
+            title
+        })
+    );
+}
+
+/**
+ * @param {Context} context
  * @param {string} commentText
  */
 async function commentIssue(context, commentText) {
diff --git a/src/issue.js b/src/issue.js
index b2e7423..87f818d 100644
--- a/src/issue.js
+++ b/src/issue.js
@@ -3,12 +3,19 @@
 const { translate } = require('./translator');
 const { removeHTMLComment } = require('./util');
 
+/**
+ * @typedef {import('probot').Context<'issues.opened'>} Context
+ */
+
 class Issue {
+    /**
+     * @param {Context} context
+     */
     constructor(context) {
         this.context = context;
         this.issue = context.payload.issue;
         this.title = this.issue.title;
-        this.body = this.issue.body;
+        this.body = this.issue.body || '';
         // null -> failed to translate -> unknown language
         // false -> translated -> not in English
         this.translatedTitle = null;
@@ -23,6 +30,13 @@
         const isCore = isCommitter(this.issue.author_association, this.issue.user.login);
 
         if (!isCore) {
+            // TODO if neither [bug] nor [feature] in title?
+            this.title = this.title.replace(
+                /(.*)(\[(?:bug|feature)\])(.*)/i,
+                function (match, p1, p2, p3) {
+                    return p2 + ' ' + p1.trim() + p3.trim()
+                }
+            );
             // check if the title is valid
             if (this.isMissingTitle()) {
                 this.addLabels.push(label.MISSING_TITLE);
@@ -60,14 +74,18 @@
     }
 
     isUsingTemplate() {
-        return this.body.indexOf('Steps to Reproduce') > -1
-            || this.body.indexOf('What problem does this feature solve') > - 1;
+        return this.body.includes('Steps to Reproduce')
+            || this.body.includes('What problem does this feature solve');
     }
 
     isMissingTitle() {
         const title = this.title.trim()
         return !title || !title.toLowerCase().replace('[bug]', '').replace('[feature]', '');
     }
+
+    isTitleChanged() {
+        return this.title !== this.issue.title;
+    }
 }
 
 module.exports = Issue;