Localize extraInfo

Fixed: twpowertools:95
Change-Id: I3ef8102948af2b9c6143b70cdf2e1c61c1b18a8f
diff --git a/src/contentScripts/communityConsole/extraInfo.js b/src/contentScripts/communityConsole/extraInfo.js
index 9b1cd9a..873a62f 100644
--- a/src/contentScripts/communityConsole/extraInfo.js
+++ b/src/contentScripts/communityConsole/extraInfo.js
@@ -13,10 +13,11 @@
 const kViewThreadResponse = 'TWPT_ViewThreadResponse';
 const kViewForumResponse = 'TWPT_ViewForumResponse';
 
+// Used to match each category with the corresponding string.
 const kAbuseCategories = [
-  ['1', 'Account'],
-  ['2', 'Display name'],
-  ['3', 'Avatar'],
+  ['1', 'account'],
+  ['2', 'displayname'],
+  ['3', 'avatar'],
 ];
 const kAbuseViolationCategories = {
   0: 'NO_VIOLATION',
@@ -25,6 +26,16 @@
   3: 'CSAI_VIOLATION',
   4: 'OTHER_VIOLATION',
 };
+const kAbuseViolationCategoriesI18n = {
+  0: 'noviolation',
+  1: 'communitypolicy',
+  2: 'legal',
+  3: 'csai',
+  4: 'other',
+};
+
+// The following array will appear in the interface as is (without being
+// translated).
 const kAbuseViolationTypes = {
   0: 'UNSPECIFIED',
   23: 'ACCOUNT_DISABLED',
@@ -145,6 +156,28 @@
   18: 'TERRORISM_SUPPORT',
   56: 'CSAI_WORST_OF_WORST',
 };
+
+// These values will be translated
+const kItemMetadataStateI18n = {
+  1: 'published',
+  2: 'draft',
+  3: 'automated_abuse_take_down_hide',
+  4: 'automated_abuse_take_down_delete',
+  13: 'automated_abuse_reinstate',
+  10: 'automated_off_topic_hide',
+  14: 'automated_flagged_pending_manual_review',
+  5: 'user_flagged_pending_manual_review',
+  6: 'owner_deleted',
+  7: 'manual_take_down_hide',
+  17: 'manual_profile_take_down_suspend',
+  8: 'manual_take_down_delete',
+  18: 'reinstate_profile_takedown',
+  9: 'reinstate_abuse_takedown',
+  11: 'clear_off_topic',
+  12: 'confirm_off_topic',
+  15: 'googler_off_topic_hide',
+  16: 'expert_flagged_pending_manual_review',
+};
 const kItemMetadataState = {
   0: 'UNDEFINED',
   1: 'PUBLISHED',
@@ -166,10 +199,6 @@
   15: 'GOOGLER_OFF_TOPIC_HIDE',
   16: 'EXPERT_FLAGGED_PENDING_MANUAL_REVIEW',
 };
-const kShadowBlockReason = {
-  0: 'REASON_UNDEFINED',
-  1: 'ULTRON_LOW_QUALITY',
-};
 
 export default class ExtraInfo {
   constructor() {
@@ -297,18 +326,6 @@
     for (const tooltip of tooltips) new MDCTooltip(tooltip);
   }
 
-  fieldInfo(field, value) {
-    let span = document.createElement('span');
-    span.append(document.createTextNode(field + ': '));
-
-    let valueEl = document.createElement('span');
-    valueEl.style.fontFamily = 'monospace';
-    valueEl.textContent = value;
-
-    span.append(valueEl);
-    return span;
-  }
-
   /**
    * Profile functionality
    */
@@ -329,10 +346,16 @@
           let info = [];
           const abuseViolationCategory = profile.body?.['1']?.['6'];
           if (abuseViolationCategory) {
-            info.push(this.fieldInfo(
-                'Abuse category',
-                kAbuseViolationCategories[abuseViolationCategory] ??
-                    abuseViolationCategory));
+            let avCat = document.createElement('span');
+            avCat.textContent = chrome.i18n.getMessage(
+                'inject_extrainfo_profile_abusecategory',
+                [chrome.i18n.getMessage(
+                     'inject_extrainfo_profile_abusecategory_' +
+                     kAbuseViolationCategoriesI18n[abuseViolationCategory]) ??
+                 abuseViolationCategory]);
+            avCat.title = kAbuseViolationCategories[abuseViolationCategory] ??
+                abuseViolationCategory;
+            info.push(avCat);
           }
 
           const profileAbuse = profile.body?.['1']?.['1']?.['8'];
@@ -340,15 +363,16 @@
           for (const [index, category] of kAbuseCategories) {
             const violation = profileAbuse?.[index]?.['1']?.['1'];
             if (violation) {
-              info.push(this.fieldInfo(
-                  category + ' policy violation',
-                  kAbuseViolationTypes[violation]));
+              info.push(chrome.i18n.getMessage(
+                  'inject_extrainfo_profile_abuse_' + category,
+                  [kAbuseViolationTypes[violation]]));
             }
           }
 
           const appealCount = profileAbuse?.['4'];
           if (appealCount !== undefined)
-            info.push(this.fieldInfo('Number of appeals', appealCount));
+            info.push(chrome.i18n.getMessage(
+                'inject_extrainfo_profile_appealsnum', [appealCount]));
 
           this.addExtraInfoElement(info, card, true);
         })
@@ -377,6 +401,13 @@
         ?.textContent;
   }
 
+  getUsageCountString(num) {
+    if (num == 1)
+      return chrome.i18n.getMessage('inject_extrainfo_crs_used_singular');
+
+    return chrome.i18n.getMessage('inject_extrainfo_crs_used_plural', [num]);
+  }
+
   // Inject usage stats in the |tags| component of a CR
   injectAtCR(tags, isExpanded) {
     waitFor(
@@ -416,7 +447,7 @@
               const [badge, badgeTooltip] = createExtBadge();
 
               let label = document.createElement('span');
-              label.textContent = 'Used ' + (cr['8'] ?? '0') + ' times';
+              label.textContent = this.getUsageCountString(cr['8'] ?? '0');
 
               content.append(badge, label);
               container.append(content);
@@ -428,7 +459,10 @@
               if (cr['9']) {
                 const lastUsedTime = Math.floor(parseInt(cr['9']) / 1e3);
                 let date = (new Date(lastUsedTime)).toLocaleString();
-                createPlainTooltip(label, 'Last used: ' + date);
+                createPlainTooltip(
+                    label,
+                    chrome.i18n.getMessage(
+                        'inject_extrainfo_crs_lastused', [date]));
               }
 
               break;
@@ -460,11 +494,15 @@
     const now = Date.now();
     if (endPendingStateTimestampMicros && endPendingStateTimestamp > now) {
       let span = document.createElement('span');
-      span.textContent = 'Only visible to badged users';
+      span.textContent =
+          chrome.i18n.getMessage('inject_extrainfo_message_pendingstate');
 
       let date = new Date(endPendingStateTimestamp).toLocaleString();
-      let pendingTooltip =
-          createPlainTooltip(span, 'Visible after ' + date, false);
+      let pendingTooltip = createPlainTooltip(
+          span,
+          chrome.i18n.getMessage(
+              'inject_extrainfo_message_pendingstate_tooltip', [date]),
+          false);
       return [span, pendingTooltip];
     }
 
@@ -475,16 +513,26 @@
     let info = [];
 
     const state = itemMetadata?.['1'];
-    if (state && state != 1)
-      info.push(this.fieldInfo('State', kItemMetadataState[state] ?? state));
+    if (state && state != 1) {
+      let span = document.createElement('span');
+      span.textContent = chrome.i18n.getMessage(
+          'inject_extrainfo_message_state',
+          [chrome.i18n.getMessage(
+               'inject_extrainfo_message_state_' +
+               kItemMetadataStateI18n[state]) ??
+           state]);
+      span.title = kItemMetadataState[state] ?? state;
+      info.push(span);
+    }
 
     const shadowBlockInfo = itemMetadata?.['10'];
     const blockedTimestampMicros = shadowBlockInfo?.['2'];
     if (blockedTimestampMicros) {
       const isBlocked = shadowBlockInfo?.['1'];
       let span = document.createElement('span');
-      span.textContent =
-          isBlocked ? 'Shadow block active' : 'Shadow block no longer active';
+      span.textContent = chrome.i18n.getMessage(
+          'inject_extrainfo_message_shadowblock' +
+          (isBlocked ? 'active' : 'notactive'));
       if (isBlocked) span.classList.add('TWPT-extrainfo-bad');
       info.push(span);
     }
@@ -498,17 +546,17 @@
     let label, labelClass;
     switch (verdict) {
       case 1:  // LIVE_REVIEW_RELEVANT
-        label = 'Relevant';
+        label = 'relevant';
         labelClass = 'TWPT-extrainfo-good';
         break;
 
       case 2:  // LIVE_REVIEW_OFF_TOPIC
-        label = 'Off-topic';
+        label = 'offtopic';
         labelClass = 'TWPT-extrainfo-bad';
         break;
 
       case 3:  // LIVE_REVIEW_ABUSE
-        label = 'Abuse';
+        label = 'abuse';
         labelClass = 'TWPT-extrainfo-bad';
         break;
     }
@@ -519,7 +567,10 @@
     let a = document.createElement('a');
     a.href = 'https://support.google.com/s/community/user/' + reviewedBy;
     a.classList.add(labelClass);
-    a.textContent = 'Live review verdict: ' + label;
+    a.textContent = chrome.i18n.getMessage(
+        'inject_extrainfo_message_livereviewverdict',
+        [chrome.i18n.getMessage(
+            'inject_extrainfo_message_livereviewverdict_' + label)]);
     let liveReviewTooltip = createPlainTooltip(a, date, false);
     return [a, liveReviewTooltip];
   }
@@ -542,9 +593,11 @@
     const isTrending = thread?.['2']?.['25'];
     const isTrendingAutoMarked = thread?.['39'];
     if (isTrendingAutoMarked)
-      info.push(document.createTextNode('Automatically marked as trending'));
+      info.push(document.createTextNode(
+          chrome.i18n.getMessage('inject_extrainfo_thread_autotrending')));
     else if (isTrending)
-      info.push(document.createTextNode('Trending'));
+      info.push(document.createTextNode(
+          chrome.i18n.getMessage('inject_extrainfo_thread_trending')));
 
     const itemMetadata = thread?.['2']?.['12'];
     const mdInfo = this.getMetadataInfo(itemMetadata);
@@ -797,7 +850,11 @@
             const [badge, badgeTooltip] = createExtBadge();
 
             let span = document.createElement('span');
-            span.textContent = kItemMetadataState[state] ?? 'State ' + state;
+            span.textContent = chrome.i18n.getMessage(
+                                   'inject_extrainfo_message_state_' +
+                                   kItemMetadataStateI18n[state]) ??
+                state;
+            span.title = kItemMetadataState[state] ?? state;
 
             label.append(badge, span);
             authorLine.prepend(label);
diff --git a/src/static/_locales/en/messages.json b/src/static/_locales/en/messages.json
index b78c8cc..12e39cd 100644
--- a/src/static/_locales/en/messages.json
+++ b/src/static/_locales/en/messages.json
@@ -397,6 +397,174 @@
     "message": "Questions",
     "description": "Label shown in the legend of the per-forum activity chart."
   },
+  "inject_extrainfo_profile_abusecategory": {
+    "message": "Abuse category: $1",
+    "description": "Message shown in profiles when there is a policy violation."
+  },
+  "inject_extrainfo_profile_abusecategory_noviolation": {
+    "message": "No violation",
+    "description": "Abuse category"
+  },
+  "inject_extrainfo_profile_abusecategory_communitypolicy": {
+    "message": "Community policy violation",
+    "description": "Abuse category"
+  },
+  "inject_extrainfo_profile_abusecategory_legal": {
+    "message": "Legal violation",
+    "description": "Abuse category"
+  },
+  "inject_extrainfo_profile_abusecategory_csai": {
+    "message": "CSAI violation",
+    "description": "Abuse category"
+  },
+  "inject_extrainfo_profile_abusecategory_other": {
+    "message": "Other violation",
+    "description": "Abuse category"
+  },
+  "inject_extrainfo_profile_abuse_account": {
+    "message": "Account policy violation: $1",
+    "description": "Message shown in profiles when there is a policy violation."
+  },
+  "inject_extrainfo_profile_abuse_displayname": {
+    "message": "Display name policy violation: $1",
+    "description": "Message shown in profiles when there is a policy violation."
+  },
+  "inject_extrainfo_profile_abuse_avatar": {
+    "message": "Avatar policy violation: $1",
+    "description": "Message shown in profiles when there is a policy violation."
+  },
+  "inject_extrainfo_profile_appealsnum": {
+    "message": "Number of appeals: $1",
+    "description": "Message shown in profiles which states how many times the account has appealed the decision to ban their profile."
+  },
+  "inject_extrainfo_crs_used_singular": {
+    "message": "Used 1 time",
+    "description": "Shown alongside a canned response to state how many times it has been used."
+  },
+  "inject_extrainfo_crs_used_plural": {
+    "message": "Used $1 times",
+    "description": "Shown alongside a canned response to state how many times it has been used."
+  },
+  "inject_extrainfo_crs_lastused": {
+    "message": "Last used: $1",
+    "description": "Tooltip which states when a canned response was last used."
+  },
+  "inject_extrainfo_message_pendingstate": {
+    "message": "Only visible to badged users",
+    "description": "Label used in a thread or message when it is in the pending state (only visible to badged users)."
+  },
+  "inject_extrainfo_message_pendingstate_tooltip": {
+    "message": "Visible after $1",
+    "description": "Tooltip used for the label \"Only visible to badged users\" to state when the thread will become visible publicly."
+  },
+  "inject_extrainfo_message_state": {
+    "message": "State: $1",
+    "description": "Label used in a thread or message to show its state."
+  },
+  "inject_extrainfo_message_state_published": {
+    "message": "Published",
+    "description": "Thread/message state PUBLISHED."
+  },
+  "inject_extrainfo_message_state_draft": {
+    "message": "Draft",
+    "description": "Thread/message state DRAFT."
+  },
+  "inject_extrainfo_message_state_automated_abuse_take_down_hide": {
+    "message": "Take down hide automated abuse",
+    "description": "Thread/message state AUTOMATED_ABUSE_TAKE_DOWN_HIDE."
+  },
+  "inject_extrainfo_message_state_automated_abuse_take_down_delete": {
+    "message": "Take down delete automated abuse",
+    "description": "Thread/message state AUTOMATED_ABUSE_TAKE_DOWN_DELETE."
+  },
+  "inject_extrainfo_message_state_automated_abuse_reinstate": {
+    "message": "Reinstate automated abuse",
+    "description": "Thread/message state AUTOMATED_ABUSE_REINSTATE."
+  },
+  "inject_extrainfo_message_state_automated_off_topic_hide": {
+    "message": "Hide automated off-topic",
+    "description": "Thread/message state AUTOMATED_OFF_TOPIC_HIDE."
+  },
+  "inject_extrainfo_message_state_automated_flagged_pending_manual_review": {
+    "message": "Automated flagged (pending manual review)",
+    "description": "Thread/message state AUTOMATED_FLAGGED_PENDING_MANUAL_REVIEW."
+  },
+  "inject_extrainfo_message_state_user_flagged_pending_manual_review": {
+    "message": "User flagged (pending manual review)",
+    "description": "Thread/message state USER_FLAGGED_PENDING_MANUAL_REVIEW."
+  },
+  "inject_extrainfo_message_state_owner_deleted": {
+    "message": "Owner deleted",
+    "description": "Thread/message state OWNER_DELETED."
+  },
+  "inject_extrainfo_message_state_manual_take_down_hide": {
+    "message": "Manual take down hide",
+    "description": "Thread/message state MANUAL_TAKE_DOWN_HIDE."
+  },
+  "inject_extrainfo_message_state_manual_profile_take_down_suspend": {
+    "message": "Manual profile take down suspend",
+    "description": "Thread/message state MANUAL_PROFILE_TAKE_DOWN_SUSPEND."
+  },
+  "inject_extrainfo_message_state_manual_take_down_delete": {
+    "message": "Manual take down delete",
+    "description": "Thread/message state MANUAL_TAKE_DOWN_DELETE."
+  },
+  "inject_extrainfo_message_state_reinstate_profile_takedown": {
+    "message": "Reinstate profile takedown",
+    "description": "Thread/message state REINSTATE_PROFILE_TAKEDOWN."
+  },
+  "inject_extrainfo_message_state_reinstate_abuse_takedown": {
+    "message": "Reinstate abuse takedown",
+    "description": "Thread/message state REINSTATE_ABUSE_TAKEDOWN."
+  },
+  "inject_extrainfo_message_state_clear_off_topic": {
+    "message": "Clear off topic",
+    "description": "Thread/message state CLEAR_OFF_TOPIC."
+  },
+  "inject_extrainfo_message_state_confirm_off_topic": {
+    "message": "Confirm off topic",
+    "description": "Thread/message state CONFIRM_OFF_TOPIC."
+  },
+  "inject_extrainfo_message_state_googler_off_topic_hide": {
+    "message": "Googler off topic hide",
+    "description": "Thread/message state GOOGLER_OFF_TOPIC_HIDE."
+  },
+  "inject_extrainfo_message_state_expert_flagged_pending_manual_review": {
+    "message": "Expert flagged (pending manual review)",
+    "description": "Thread/message state EXPERT_FLAGGED_PENDING_MANUAL_REVIEW."
+  },
+  "inject_extrainfo_message_shadowblockactive": {
+    "message": "Shadow block active",
+    "description": "Label used in a thread or message when it has been shadow blocked and is thus hidden from view."
+  },
+  "inject_extrainfo_message_shadowblocknotactive": {
+    "message": "Shadow block no longer active",
+    "description": "Label used in a thread or message when it had been shadow blocked but it isn't active anymore."
+  },
+  "inject_extrainfo_message_livereviewverdict": {
+    "message": "Live review verdict: $1",
+    "description": "Label used in a thread or message to show what was the action taken when live reviewing it."
+  },
+  "inject_extrainfo_message_livereviewverdict_relevant": {
+    "message": "Relevant",
+    "description": "One of the actions of a live review."
+  },
+  "inject_extrainfo_message_livereviewverdict_offtopic": {
+    "message": "Off topic",
+    "description": "One of the actions of a live review."
+  },
+  "inject_extrainfo_message_livereviewverdict_abuse": {
+    "message": "Abuse",
+    "description": "One of the actions of a live review."
+  },
+  "inject_extrainfo_thread_autotrending": {
+    "message": "Automatically marked as trending",
+    "description": "Label used in a thread to show it has been automatically marked as trending."
+  },
+  "inject_extrainfo_thread_trending": {
+    "message": "Trending",
+    "description": "Label used in a thread to show it has been manually marked as trending."
+  },
   "actionbadge_permissions_requested": {
     "message": "Some features need additional permissions to work. Click to fix it.",
     "description": "Tooltip for the extension icon when a feature is enabled but it needs several permissions to be granted."