perForumStats: add strings localization

Bug: twpowertools:95
Change-Id: I98bd6d3d51fbd4ff4706eab596a8dd895de28f14
diff --git a/src/contentScripts/communityConsole/extraInfo.js b/src/contentScripts/communityConsole/extraInfo.js
index b78b2d0..8419ea2 100644
--- a/src/contentScripts/communityConsole/extraInfo.js
+++ b/src/contentScripts/communityConsole/extraInfo.js
@@ -6,6 +6,7 @@
 import {createPlainTooltip} from '../../common/tooltip.js';
 
 import {createExtBadge, getDisplayLanguage} from './utils/common.js';
+import PerForumStatsSection from './utils/PerForumStatsSection.js';
 
 const kViewUnifiedUserResponseEvent = 'TWPT_ViewUnifiedUserResponse';
 const kListCannedResponsesResponse = 'TWPT_ListCannedResponsesResponse';
@@ -719,13 +720,8 @@
           timeout: 15 * 1000,
         })
         .then(profile => {
-          const message = {
-            action: 'injectPerForumStatsSection',
-            prefix: 'TWPT-extrainfo',
-            profile: profile.body,
-            locale: this.displayLanguage,
-          };
-          window.postMessage(message, '*');
+          new PerForumStatsSection(
+              chart?.parentNode, profile.body, this.displayLanguage);
         })
         .catch(err => {
           console.error(
diff --git a/src/contentScripts/communityConsole/utils/PerForumStatsSection.js b/src/contentScripts/communityConsole/utils/PerForumStatsSection.js
index aaa553c..552dece 100644
--- a/src/contentScripts/communityConsole/utils/PerForumStatsSection.js
+++ b/src/contentScripts/communityConsole/utils/PerForumStatsSection.js
@@ -1,12 +1,11 @@
 // Each entry includes the following information in order:
 // - ID
-// - Name (for the label in the legend)
 // - Codename
 // - Color (for the label in the legend)
 const kDataKeys = [
-  [4, 'Recommended', 'recommended', '#34A853'],
-  [6, 'Replies (not recommended)', 'replies', '#DADCE0'],
-  [5, 'Questions', 'questions', '#77909D'],
+  [4, 'recommended', '#34A853'],
+  [6, 'replies', '#DADCE0'],
+  [5, 'questions', '#77909D'],
 ];
 const kRoles = {
   1: 'bronze',
@@ -22,11 +21,6 @@
 
 export default class PerForumStatsSection {
   constructor(existingChartSection, profile, locale) {
-    if (typeof window.sc_renderProfileActivityChart !== 'function') {
-      console.error(
-          'PerForumStatsSection: window.sc_renderProfileActivityChart is not available.');
-      return;
-    }
     this.locale = locale;
     this.parseAndSetData(profile);
     this.buildDOM(existingChartSection);
@@ -79,23 +73,22 @@
 
     let title = document.createElement('h2');
     title.classList.add('scTailwindSharedActivitycharttitle');
-    title.textContent = 'Per-forum activity';
+    title.textContent = chrome.i18n.getMessage('inject_perforumstats_heading');
 
     let selector = this.createForumSelector();
 
     let chartEl = document.createElement('div');
     chartEl.classList.add('scTailwindSharedActivitychartchart');
+    chartEl.setAttribute('data-twpt-per-forum-chart', '');
 
     root.append(title, selector, chartEl);
     section.append(root);
     existingChartSection.after(section);
-
-    this.chartEl = chartEl;
   }
 
   getAplosData(forumId) {
     let aplosData = [];
-    for (const [key, label, name, color] of kDataKeys) {
+    for (const [key, name, color] of kDataKeys) {
       let rawData = this.data.find(f => f.id === forumId)?.forumUserInfo?.[key];
       let data;
       if (!rawData)
@@ -105,13 +98,35 @@
       aplosData.push({
         color,
         data,
-        label,
+        label: chrome.i18n.getMessage('inject_perforumstats_chart_' + name),
         name,
       });
     }
     return aplosData;
   }
 
+  getMessagesString(num) {
+    if (num == 1) {
+      return chrome.i18n.getMessage(
+          'inject_perforumstats_nummessages_singular');
+    }
+    return chrome.i18n.getMessage(
+        'inject_perforumstats_nummessages_plural', [num]);
+  }
+
+  getForumOptionString(forumTitle, labels) {
+    if (labels.length == 0) return forumTitle;
+    if (labels.length == 1)
+      return chrome.i18n.getMessage(
+          'inject_perforumstats_forumoption_1helper', [forumTitle, ...labels]);
+    if (labels.length == 2)
+      return chrome.i18n.getMessage(
+          'inject_perforumstats_forumoption_2helpers', [forumTitle, ...labels]);
+
+    // If labels.length > 3, this is unexpected. Here's a sensible fallback:
+    return forumTitle + ' (' + labels.join(', ') + ')';
+  }
+
   createForumSelector() {
     let div = document.createElement('div');
     div.classList.add('TWPT-select-container');
@@ -124,21 +139,22 @@
 
       if (!hasPosted && !noPostsGroupFlag) {
         noPostsGroup = document.createElement('optgroup');
-        noPostsGroup.label = 'Not posted to within the last 12 months';
+        noPostsGroup.label =
+            chrome.i18n.getMessage('inject_perforumstats_optgroup_notposted');
         noPostsGroupFlag = true;
       }
 
-      let additionalLabelsArray = [];
+      let additionalLabels = [];
       if (hasPosted)
-        additionalLabelsArray.push(forumData.numMessages + ' messages');
+        additionalLabels.push(this.getMessagesString(forumData.numMessages));
       let role = forumData.forumUserInfo?.[1]?.[3] ?? 0;
-      if (role) additionalLabelsArray.push(kRoles[role]);
-      let additionalLabels = '';
-      if (additionalLabelsArray.length > 0)
-        additionalLabels = ' (' + additionalLabelsArray.join(', ') + ')';
+      if (role)
+        additionalLabels.push(chrome.i18n.getMessage(
+            'inject_perforumstats_role_' + kRoles[role]));
 
       let option = document.createElement('option');
-      option.textContent = forumData.forumTitle + additionalLabels;
+      option.textContent =
+          this.getForumOptionString(forumData.forumTitle, additionalLabels);
       option.value = forumData.id;
       if (hasPosted)
         select.append(option);
@@ -156,8 +172,6 @@
   }
 
   injectChart(forumId) {
-    this.chartEl.replaceChildren();
-
     let data = this.getAplosData(forumId);
     let metadata = {
       activities: [],
@@ -165,8 +179,14 @@
       locale: this.locale,
       shouldDisableTransitions: true,
     };
-    let chartTitle = 'User activity chart';
-    let chart = window.sc_renderProfileActivityChart(
-        this.chartEl, data, metadata, chartTitle);
+    let chartTitle = chrome.i18n.getMessage('inject_perforumstats_chart_label');
+    const message = {
+      action: 'renderProfileActivityChart',
+      prefix: 'TWPT-extrainfo',
+      data,
+      metadata,
+      chartTitle,
+    };
+    window.postMessage(message, '*');
   }
 }
diff --git a/src/injections/extraInfo.js b/src/injections/extraInfo.js
index c9c6e2c..3758314 100644
--- a/src/injections/extraInfo.js
+++ b/src/injections/extraInfo.js
@@ -1,20 +1,23 @@
-import PerForumStatsSection from '../contentScripts/communityConsole/utils/PerForumStatsSection.js';
-
 window.addEventListener('message', e => {
   if (e.source === window && e.data?.prefix === 'TWPT-extrainfo') {
     switch (e.data?.action) {
-      case 'injectPerForumStatsSection':
-        let existingChartSection =
-            document
-                .querySelector(
-                    'sc-tailwind-user_profile-user-profile sc-tailwind-shared-activity-chart')
-                ?.parentNode;
-        if (!existingChartSection) {
-          console.error('extraInfo: couldn\'t find existing chart section.');
+      case 'renderProfileActivityChart':
+        if (typeof window.sc_renderProfileActivityChart !== 'function') {
+          console.error(
+              'extraInfo: window.sc_renderProfileActivityChart is not available.');
           return;
         }
-        new PerForumStatsSection(
-            existingChartSection, e.data?.profile, e.data?.locale);
+
+        let chartEl = document.querySelector(
+            '.scTailwindSharedActivitychartchart[data-twpt-per-forum-chart]');
+        if (!chartEl) {
+          console.error('extraInfo: couldn\'t find per-forum chart div.');
+          return;
+        }
+
+        chartEl.replaceChildren();
+        window.sc_renderProfileActivityChart(
+            chartEl, e.data?.data, e.data?.metadata, e.data?.chartTitle);
         break;
 
       default:
diff --git a/src/static/_locales/en/messages.json b/src/static/_locales/en/messages.json
index 44271db..f64219d 100644
--- a/src/static/_locales/en/messages.json
+++ b/src/static/_locales/en/messages.json
@@ -287,6 +287,112 @@
     "message": "Run a workflow...",
     "description": "Tooltip of the icon shown above a thread or in thread lists when selecting multiple threads in the Community Console which lets the user show a menu with the worklofws they can run."
   },
+  "inject_perforumstats_heading": {
+    "message": "Per-forum activity",
+    "description": "Heading of the section with the per-forum activity stacked bars graph showing the number of questions, replies and recommended answers each month."
+  },
+  "inject_perforumstats_forumoption_1helper": {
+    "message": "$forumName$ ($helper1$)",
+    "description": "In the selector where the user can select the forum shown in the graph, one such option. $helper1$ will usually be the number of messages posted by the user in that forum.",
+    "placeholders": {
+      "forumName": {
+        "content": "$1",
+        "example": "Google Chrome"
+      },
+      "helper1": {
+        "content": "$2",
+        "example": "963 messages"
+      }
+    }
+  },
+  "inject_perforumstats_forumoption_2helpers": {
+    "message": "$forumName$ ($helper1$, $helper2$)",
+    "description": "In the selector where the user can select the forum shown in the graph, one such option. $helper1$ will be the number of messages posted by the user in that forum, and $helper2$ will be the badge awarded to the user in that forum.",
+    "placeholders": {
+      "forumName": {
+        "content": "$1",
+        "example": "Google Chrome"
+      },
+      "helper1": {
+        "content": "$2",
+        "example": "963 messages"
+      },
+      "helper2": {
+        "content": "$3",
+        "example": "gold"
+      }
+    }
+  },
+  "inject_perforumstats_nummessages_singular": {
+    "message": "1 message",
+    "description": "Annotation shown in the forum selector which states how many messages have been published by a user in a forum (singular variant)."
+  },
+  "inject_perforumstats_nummessages_plural": {
+    "message": "$num$ messages",
+    "description": "Annotation shown in the forum selector which states how many messages have been published by a user in a forum (plural variant).",
+    "placeholders": {
+      "num": {
+        "content": "$1",
+        "example": "963"
+      }
+    }
+  },
+  "inject_perforumstats_role_bronze": {
+    "message": "bronze",
+    "description": "Role that a user can have in the forums."
+  },
+  "inject_perforumstats_role_silver": {
+    "message": "silver",
+    "description": "Role that a user can have in the forums."
+  },
+  "inject_perforumstats_role_gold": {
+    "message": "gold",
+    "description": "Role that a user can have in the forums."
+  },
+  "inject_perforumstats_role_platinum": {
+    "message": "platinum",
+    "description": "Role that a user can have in the forums."
+  },
+  "inject_perforumstats_role_diamond": {
+    "message": "diamond",
+    "description": "Role that a user can have in the forums."
+  },
+  "inject_perforumstats_role_community_manager": {
+    "message": "CM",
+    "description": "Role that a user can have in the forums (short for community manager)."
+  },
+  "inject_perforumstats_role_community_specialist": {
+    "message": "specialist",
+    "description": "Role that a user can have in the forums (short for community specialist)."
+  },
+  "inject_perforumstats_role_google_employee": {
+    "message": "Googler",
+    "description": "Role that a user can have in the forums."
+  },
+  "inject_perforumstats_role_alumnus": {
+    "message": "alumnus",
+    "description": "Role that a user can have in the forums."
+  },
+  "inject_perforumstats_optgroup_notposted": {
+    "message": "Not posted to within the last 12 months",
+    "description": "Label showed above the forums for which the user hasn't posted in the last 12 months, in the forum selector list."
+  },
+  "inject_perforumstats_chart_label": {
+    "message": "User activity chart",
+    "description": "Label for the chart for accessibility purposes (e.g. users with screen-readers)."
+  },
+  "inject_perforumstats_chart_recommended": {
+    "message": "Recommended",
+    "description": "Label shown in the legend of the per-forum activity chart."
+  },
+  "inject_perforumstats_chart_replies": {
+    "message": "Replies (not recommended)",
+    "description": "Label shown in the legend of the per-forum activity chart."
+  },
+  "inject_perforumstats_chart_questions": {
+    "message": "Questions",
+    "description": "Label shown in the legend of the per-forum activity chart."
+  },
   "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."