Repository: anuraghazra/github-readme-stats
Pull request #3215 (2023-09-10 08:39:50)

fix: Simplified Chinese error
As a Simplified Chinese user, if you are unsure, it should have been Traditional Chinese or something else

vercel[bot]: @weiensong is attempting to deploy a commit to the **github readme stats** Team on [Vercel](https://vercel.com).

A member of the Team first needs to [authorize it](https://vercel.com/git/authorize?team=github%20readme%20stats&type=github&job=%7B%22headInfo%22%3A%7B%22sha%22%3A%2204e2fa09572fdcdd536ef81ec76670f0e051fb04%22%7D%2C%22id%22%3A%22QmSS4LxWJQgiFo88Mz1g91HwFB6HWC1Y4zR4w9AcBB1he2%22%2C%22org%22%3A%22anuraghazra%22%2C%22prId%22%3A3215%2C%22repo%22%3A%22github-readme-stats%22%7D).



Patch:
diff --git a/.eslintrc.json b/.eslintrc.json
index 8be615b..88030d9 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -39,7 +39,7 @@
         }],
         "block-scoped-var": "warn",
         "consistent-return": "error",
-        // "curly": "error",
+        "curly": "error",
         // "default-case": "warn",
 
         // the dot goes with the property when doing multiline
@@ -94,10 +94,10 @@
 
         // Produce warnings when something is commented as TODO or FIXME
 
-        // "no-warning-comments": [ "warn", {
-        //     "terms": [ "TODO", "FIXME" ],
-        //     "location": "start"
-        // }],
+        "no-warning-comments": [ "warn", {
+            "terms": [ "TODO", "FIXME" ],
+            "location": "start"
+        }],
         // "no-with": "warn",
         // "radix": "warn",
         // "vars-on-top": "error",
diff --git a/.github/workflows/empty-issues-closer.yaml b/.github/workflows/empty-issues-closer.yaml
index 9de2ebc..e675faf 100644
--- a/.github/workflows/empty-issues-closer.yaml
+++ b/.github/workflows/empty-issues-closer.yaml
@@ -30,7 +30,7 @@ jobs:
       - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
 
       - name: Run empty issues closer action
-        uses: rickstaa/empty-issues-closer-action@1f356958abb6f3ead7f1ee451b4129aa0c31efe7 # v1.1.18
+        uses: rickstaa/empty-issues-closer-action@2976d7763c8490535b108c414d8a147ffea86f28 # v1.1.20
         env:
           github_token: ${{ secrets.GITHUB_TOKEN }}
         with:
diff --git a/.github/workflows/ossf-analysis.yml b/.github/workflows/ossf-analysis.yml
index 9444db2..be6576d 100644
--- a/.github/workflows/ossf-analysis.yml
+++ b/.github/workflows/ossf-analysis.yml
@@ -35,7 +35,7 @@ jobs:
       # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
       # format to the repository Actions tab.
       - name: "Upload artifact"
-        uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
+        uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
         with:
           name: SARIF file
           path: results.sarif
diff --git a/.github/workflows/top-issues-dashboard.yml b/.github/workflows/top-issues-dashboard.yml
index ef93656..46be04a 100644
--- a/.github/workflows/top-issues-dashboard.yml
+++ b/.github/workflows/top-issues-dashboard.yml
@@ -34,7 +34,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Run top issues action
-        uses: rickstaa/top-issues-action@0444fe7a3c3ed5fbf4c3ff3dec3585ebe24e1338 # v1.3.44
+        uses: rickstaa/top-issues-action@20822b6d133fa88780e87f154bef34a6911005ec # v1.3.46
         env:
           github_token: ${{ secrets.GITHUB_TOKEN }}
         with:
diff --git a/docs/readme_cn.md b/docs/readme_cn.md
index 386f904..f33f98d 100644
--- a/docs/readme_cn.md
+++ b/docs/readme_cn.md
@@ -189,7 +189,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
 
 - `hide` - 从卡片中隐藏指定语言 _(Comma seperated values)_
 - `hide_title` - _(boolean)_
-- `layout` - 提供五種佈局 `normal` & `compact` & `donut` & `donut-vertical` & `pie` 间切换
+- `layout` - 提供五种布局 `normal` & `compact` & `donut` & `donut-vertical` & `pie` 间切换
 - `card_width` - 手动设置卡片的宽度 _(number)_
 
 > :warning: **重要:**
@@ -321,7 +321,7 @@ _注意：热门语言并不表示我的技能水平或类似的水平，它是
 
 ## 自己部署
 
-#### [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107)
+#### [查看分步视频教程 作者：@codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107)
 
 因为 GitHub 的 API 每个小时只允许 5 千次请求，我的 `https://github-readme-stats.vercel.app/api` 很有可能会触发限制。如果你将其托管在自己的 Vercel 服务器上，那么你就不必为此担心。点击 deploy 按钮来开始你的部署！
 
diff --git a/package-lock.json b/package-lock.json
index 5edaaaf..2fff2e7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,14 +17,14 @@
         "word-wrap": "^1.2.5"
       },
       "devDependencies": {
-        "@actions/core": "^1.10.0",
+        "@actions/core": "^1.10.1",
         "@actions/github": "^5.1.1",
         "@testing-library/dom": "^9.3.1",
-        "@testing-library/jest-dom": "^6.1.2",
+        "@testing-library/jest-dom": "^6.1.3",
         "@uppercod/css-to-object": "^1.1.1",
-        "axios-mock-adapter": "^1.21.5",
+        "axios-mock-adapter": "^1.22.0",
         "color-contrast-checker": "^2.1.0",
-        "eslint": "^8.48.0",
+        "eslint": "^8.49.0",
         "eslint-config-prettier": "^9.0.0",
         "hjson": "^3.2.2",
         "husky": "^8.0.3",
@@ -50,9 +50,9 @@
       }
     },
     "node_modules/@actions/core": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
-      "integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
+      "version": "1.10.1",
+      "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz",
+      "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==",
       "dev": true,
       "dependencies": {
         "@actions/http-client": "^2.0.1",
@@ -745,18 +745,18 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "8.48.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz",
-      "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==",
+      "version": "8.49.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz",
+      "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
       }
     },
     "node_modules/@humanwhocodes/config-array": {
-      "version": "0.11.10",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
-      "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
+      "version": "0.11.11",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
+      "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
       "dev": true,
       "dependencies": {
         "@humanwhocodes/object-schema": "^1.2.1",
@@ -1436,9 +1436,9 @@
       }
     },
     "node_modules/@testing-library/jest-dom": {
-      "version": "6.1.2",
-      "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.2.tgz",
-      "integrity": "sha512-NP9jl1Q2qDDtx+cqogowtQtmgD2OVs37iMSIsTv5eN5ETRkf26Kj6ugVwA93/gZzzFWQAsgkKkcftDe91BJCkQ==",
+      "version": "6.1.3",
+      "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.3.tgz",
+      "integrity": "sha512-YzpjRHoCBWPzpPNtg6gnhasqtE/5O4qz8WCwDEaxtfnPO6gkaLrnuXusrGSPyhIGPezr1HM7ZH0CFaUTY9PJEQ==",
       "dev": true,
       "dependencies": {
         "@adobe/css-tools": "^4.3.0",
@@ -1810,9 +1810,9 @@
       }
     },
     "node_modules/axios-mock-adapter": {
-      "version": "1.21.5",
-      "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.21.5.tgz",
-      "integrity": "sha512-5NI1V/VK+8+JeTF8niqOowuysA4b8mGzdlMN/QnTnoXbYh4HZSNiopsDclN2g/m85+G++IrEtUdZaQ3GnaMsSA==",
+      "version": "1.22.0",
+      "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz",
+      "integrity": "sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==",
       "dev": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.3",
@@ -2619,16 +2619,16 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.48.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz",
-      "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==",
+      "version": "8.49.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz",
+      "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
         "@eslint/eslintrc": "^2.1.2",
-        "@eslint/js": "8.48.0",
-        "@humanwhocodes/config-array": "^0.11.10",
+        "@eslint/js": "8.49.0",
+        "@humanwhocodes/config-array": "^0.11.11",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
         "ajv": "^6.12.4",
@@ -7028,9 +7028,9 @@
       "dev": true
     },
     "@actions/core": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
-      "integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
+      "version": "1.10.1",
+      "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz",
+      "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==",
       "dev": true,
       "requires": {
         "@actions/http-client": "^2.0.1",
@@ -7551,15 +7551,15 @@
       }
     },
     "@eslint/js": {
-      "version": "8.48.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz",
-      "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==",
+      "version": "8.49.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz",
+      "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==",
       "dev": true
     },
     "@humanwhocodes/config-array": {
-      "version": "0.11.10",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
-      "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
+      "version": "0.11.11",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
+      "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==",
       "dev": true,
       "requires": {
         "@humanwhocodes/object-schema": "^1.2.1",
@@ -8122,9 +8122,9 @@
       }
     },
     "@testing-library/jest-dom": {
-      "version": "6.1.2",
-      "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.2.tgz",
-      "integrity": "sha512-NP9jl1Q2qDDtx+cqogowtQtmgD2OVs37iMSIsTv5eN5ETRkf26Kj6ugVwA93/gZzzFWQAsgkKkcftDe91BJCkQ==",
+      "version": "6.1.3",
+      "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.1.3.tgz",
+      "integrity": "sha512-YzpjRHoCBWPzpPNtg6gnhasqtE/5O4qz8WCwDEaxtfnPO6gkaLrnuXusrGSPyhIGPezr1HM7ZH0CFaUTY9PJEQ==",
       "dev": true,
       "requires": {
         "@adobe/css-tools": "^4.3.0",
@@ -8422,9 +8422,9 @@
       }
     },
     "axios-mock-adapter": {
-      "version": "1.21.5",
-      "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.21.5.tgz",
-      "integrity": "sha512-5NI1V/VK+8+JeTF8niqOowuysA4b8mGzdlMN/QnTnoXbYh4HZSNiopsDclN2g/m85+G++IrEtUdZaQ3GnaMsSA==",
+      "version": "1.22.0",
+      "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz",
+      "integrity": "sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==",
       "dev": true,
       "requires": {
         "fast-deep-equal": "^3.1.3",
@@ -9014,16 +9014,16 @@
       }
     },
     "eslint": {
-      "version": "8.48.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz",
-      "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==",
+      "version": "8.49.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz",
+      "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==",
       "dev": true,
       "requires": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
         "@eslint/eslintrc": "^2.1.2",
-        "@eslint/js": "8.48.0",
-        "@humanwhocodes/config-array": "^0.11.10",
+        "@eslint/js": "8.49.0",
+        "@humanwhocodes/config-array": "^0.11.11",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
         "ajv": "^6.12.4",
diff --git a/package.json b/package.json
index f856e9f..cf54c78 100644
--- a/package.json
+++ b/package.json
@@ -35,14 +35,14 @@
   "author": "Anurag Hazra",
   "license": "MIT",
   "devDependencies": {
-    "@actions/core": "^1.10.0",
+    "@actions/core": "^1.10.1",
     "@actions/github": "^5.1.1",
     "@testing-library/dom": "^9.3.1",
-    "@testing-library/jest-dom": "^6.1.2",
+    "@testing-library/jest-dom": "^6.1.3",
     "@uppercod/css-to-object": "^1.1.1",
-    "axios-mock-adapter": "^1.21.5",
+    "axios-mock-adapter": "^1.22.0",
     "color-contrast-checker": "^2.1.0",
-    "eslint": "^8.48.0",
+    "eslint": "^8.49.0",
     "eslint-config-prettier": "^9.0.0",
     "hjson": "^3.2.2",
     "husky": "^8.0.3",
diff --git a/readme.md b/readme.md
index 60747cb..f0f5fce 100644
--- a/readme.md
+++ b/readme.md
@@ -426,6 +426,9 @@ The top languages card shows a GitHub user's most frequently used languages.
 > [!WARNING]\
 > This card shows languages usage only inside your own non-forked repositories, not depending from who is the author of the commits. It does not include your contributions into another users/organizations repositories. Currently there are no way to get this data from GitHub API. If you want this behavior to be improved you can support [this feature request](https://github.com/orgs/community/discussions/18230) created by [@rickstaa](https://github.com/rickstaa) inside GitHub Community.
 
+> [!WARNING]\
+> Currently this card shows data only about first 100 repositories. This is because GitHub API limitations which cause downtimes of public instance (see [#1471](https://github.com/anuraghazra/github-readme-stats/issues/1471)). In future this behavior will be improved by releasing GitHub action or providing environment variable for user's own instances.
+
 ### Usage
 
 Copy-paste this code into your readme and change the links.
diff --git a/scripts/close-stale-theme-prs.js b/scripts/close-stale-theme-prs.js
index f338340..3b513f1 100644
--- a/scripts/close-stale-theme-prs.js
+++ b/scripts/close-stale-theme-prs.js
@@ -156,13 +156,13 @@ const run = async () => {
     for (const prNumber of staleThemePRsNumbers) {
       debug(`Closing #${prNumber} because it is stale...`);
       if (!dryRun) {
-        await octokit.issues.createComment({
+        await octokit.rest.issues.createComment({
           owner,
           repo,
           issue_number: prNumber,
           body: CLOSING_COMMENT,
         });
-        await octokit.pulls.update({
+        await octokit.rest.pulls.update({
           owner,
           repo,
           pull_number: prNumber,
diff --git a/scripts/generate-theme-doc.js b/scripts/generate-theme-doc.js
index 8ef3c20..d29026e 100644
--- a/scripts/generate-theme-doc.js
+++ b/scripts/generate-theme-doc.js
@@ -60,9 +60,12 @@ const generateLinks = (fn) => {
 };
 
 const createTableItem = ({ link, label, isRepoCard }) => {
-  if (!link || !label) return "";
+  if (!link || !label) {
+    return "";
+  }
   return `\`${label}\` ![${link}][${link}${isRepoCard ? "_repo" : ""}]`;
 };
+
 const generateTable = ({ isRepoCard }) => {
   const rows = [];
   const themesFiltered = Object.keys(themes).filter(
diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js
index 6528fcf..fd55d57 100644
--- a/scripts/preview-theme.js
+++ b/scripts/preview-theme.js
@@ -75,7 +75,9 @@ class IncorrectJsonFormatError extends Error {
  * @returns {number} PR number.
  */
 const getPrNumber = () => {
-  if (process.env.MOCK_PR_NUMBER) return process.env.MOCK_PR_NUMBER; // For testing purposes.
+  if (process.env.MOCK_PR_NUMBER) {
+    return process.env.MOCK_PR_NUMBER; // For testing purposes.
+  }
 
   const pullRequest = github.context.payload.pull_request;
   if (!pullRequest) {
diff --git a/src/cards/gist-card.js b/src/cards/gist-card.js
index 37e1118..9e889e7 100644
--- a/src/cards/gist-card.js
+++ b/src/cards/gist-card.js
@@ -8,6 +8,8 @@ import {
   kFormatter,
   measureText,
   flexLayout,
+  iconWithLabel,
+  createLanguageNode,
 } from "../common/utils.js";
 import Card from "../common/Card.js";
 import { icons } from "../common/icons.js";
@@ -27,48 +29,6 @@ const ICON_SIZE = 16;
 const CARD_DEFAULT_WIDTH = 400;
 const HEADER_MAX_LENGTH = 35;
 
-/**
- * Creates a node to display the primary programming language of the gist.
- *
- * @param {string} langName Language name.
- * @param {string} langColor Language color.
- * @returns {string} Language display SVG object.
- */
-const createLanguageNode = (langName, langColor) => {
-  return `
-    <g data-testid="primary-lang">
-      <circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
-      <text data-testid="lang-name" class="gray" x="15">${langName}</text>
-    </g>
-    `;
-};
-
-/**
- * Creates an icon with label to display gist stats like forks, stars, etc.
- *
- * @param {string} icon The icon to display.
- * @param {number|string} label The label to display.
- * @param {string} testid The testid to assign to the label.
- * @returns {string} Icon with label SVG object.
- */
-const iconWithLabel = (icon, label, testid) => {
-  if (typeof label === "number" && label <= 0) return "";
-  const iconSvg = `
-      <svg
-        class="icon"
-        y="-12"
-        viewBox="0 0 16 16"
-        version="1.1"
-        width="${ICON_SIZE}"
-        height="${ICON_SIZE}"
-      >
-        ${icon}
-      </svg>
-    `;
-  const text = `<text data-testid="${testid}" class="gray">${label}</text>`;
-  return flexLayout({ items: [iconSvg, text], gap: 20 }).join("");
-};
-
 /**
  * @typedef {import('./types').GistCardOptions} GistCardOptions Gist card options.
  * @typedef {import('../fetchers/types').GistData} GistData Gist data.
@@ -122,8 +82,18 @@ const renderGistCard = (gistData, options = {}) => {
 
   const totalStars = kFormatter(starsCount);
   const totalForks = kFormatter(forksCount);
-  const svgStars = iconWithLabel(icons.star, totalStars, "starsCount");
-  const svgForks = iconWithLabel(icons.fork, totalForks, "forksCount");
+  const svgStars = iconWithLabel(
+    icons.star,
+    totalStars,
+    "starsCount",
+    ICON_SIZE,
+  );
+  const svgForks = iconWithLabel(
+    icons.fork,
+    totalForks,
+    "forksCount",
+    ICON_SIZE,
+  );
 
   const languageName = language || "Unspecified";
   const languageColor = languageColors[languageName] || "#858585";
diff --git a/src/cards/repo-card.js b/src/cards/repo-card.js
index 3706c96..09b5841 100644
--- a/src/cards/repo-card.js
+++ b/src/cards/repo-card.js
@@ -10,9 +10,13 @@ import {
   measureText,
   parseEmojis,
   wrapTextMultiline,
+  iconWithLabel,
+  createLanguageNode,
 } from "../common/utils.js";
 import { repoCardLocales } from "../translations.js";
 
+const ICON_SIZE = 16;
+
 /**
  * Retrieves the repository description and wraps it to fit the card width.
  *
@@ -35,50 +39,6 @@ const getBadgeSVG = (label, textColor) => `
   </g>
 `;
 
-/**
- * Creates a node to display the primary programming language of the repository.
- *
- * @param {string} langName Language name.
- * @param {string} langColor Language color.
- * @returns {string} Language display SVG object.
- */
-const createLanguageNode = (langName, langColor) => {
-  return `
-  <g data-testid="primary-lang">
-    <circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
-    <text data-testid="lang-name" class="gray" x="15">${langName}</text>
-  </g>
-  `;
-};
-
-const ICON_SIZE = 16;
-
-/**
- * Creates an icon with label to display repository stats like forks, stars, etc.
- *
- * @param {string} icon The icon to display.
- * @param {number|string} label The label to display.
- * @param {string} testid The testid to assign to the label.
- * @returns {string} Icon with label SVG object.
- */
-const iconWithLabel = (icon, label, testid) => {
-  if (typeof label === "number" && label <= 0) return "";
-  const iconSvg = `
-    <svg
-      class="icon"
-      y="-12"
-      viewBox="0 0 16 16"
-      version="1.1"
-      width="${ICON_SIZE}"
-      height="${ICON_SIZE}"
-    >
-      ${icon}
-    </svg>
-  `;
-  const text = `<text data-testid="${testid}" class="gray">${label}</text>`;
-  return flexLayout({ items: [iconSvg, text], gap: 20 }).join("");
-};
-
 /**
  * @typedef {import("../fetchers/types").RepositoryData} RepositoryData Repository data.
  * @typedef {import("./types").RepoCardOptions} RepoCardOptions Repo card options.
@@ -151,8 +111,18 @@ const renderRepoCard = (repo, options = {}) => {
 
   const totalStars = kFormatter(starCount);
   const totalForks = kFormatter(forkCount);
-  const svgStars = iconWithLabel(icons.star, totalStars, "stargazers");
-  const svgForks = iconWithLabel(icons.fork, totalForks, "forkcount");
+  const svgStars = iconWithLabel(
+    icons.star,
+    totalStars,
+    "stargazers",
+    ICON_SIZE,
+  );
+  const svgForks = iconWithLabel(
+    icons.fork,
+    totalForks,
+    "forkcount",
+    ICON_SIZE,
+  );
 
   const starAndForkCount = flexLayout({
     items: [svgLanguage, svgStars, svgForks],
diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js
index 778e676..dad81bb 100644
--- a/src/cards/stats-card.js
+++ b/src/cards/stats-card.js
@@ -358,7 +358,9 @@ const renderStatsCard = (stats, options = {}) => {
   card.setHideTitle(hide_title);
   card.setCSS(cssStyles);
 
-  if (disable_animations) card.disableAnimations();
+  if (disable_animations) {
+    card.disableAnimations();
+  }
 
   /**
    * Calculates the right rank circle translation values such that the rank circle
diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js
index f2fda70..9593f98 100644
--- a/src/cards/top-languages-card.js
+++ b/src/cards/top-languages-card.js
@@ -87,7 +87,9 @@ const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
 const cartesianToPolar = (centerX, centerY, x, y) => {
   const radius = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
   let angleInDegrees = radiansToDegrees(Math.atan2(y - centerY, x - centerX));
-  if (angleInDegrees < 0) angleInDegrees += 360;
+  if (angleInDegrees < 0) {
+    angleInDegrees += 360;
+  }
   return { radius, angleInDegrees };
 };
 
@@ -201,7 +203,7 @@ const trimTopLanguages = (topLangs, langs_count, hide) => {
  * @param {number} props.width The card width
  * @param {string} props.color Color of the programming language.
  * @param {string} props.name Name of the programming language.
- * @param {string} props.progress Usage of the programming language in percentage.
+ * @param {number} props.progress Usage of the programming language in percentage.
  * @param {number} props.index Index of the programming language.
  * @returns {string} Programming language SVG node.
  */
@@ -329,7 +331,9 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => {
         width,
         name: lang.name,
         color: lang.color || DEFAULT_LANG_COLOR,
-        progress: ((lang.size / totalLanguageSize) * 100).toFixed(2),
+        progress: parseFloat(
+          ((lang.size / totalLanguageSize) * 100).toFixed(2),
+        ),
         index,
       });
     }),
@@ -806,7 +810,9 @@ const renderTopLanguages = (topLangs, options = {}) => {
     colors,
   });
 
-  if (disable_animations) card.disableAnimations();
+  if (disable_animations) {
+    card.disableAnimations();
+  }
 
   card.setHideBorder(hide_border);
   card.setHideTitle(hide_title);
diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js
index e3f5433..187af7e 100644
--- a/src/cards/wakatime-card.js
+++ b/src/cards/wakatime-card.js
@@ -95,7 +95,7 @@ const createLanguageTextNode = ({ langs, y }) => {
  * @param {string} args.label The label of the text node item.
  * @param {string} args.value The value of the text node item.
  * @param {number} args.index The index of the text node item.
- * @param {string} args.percent Percentage of the text node item.
+ * @param {number} args.percent Percentage of the text node item.
  * @param {boolean=} args.hideProgress Whether to hide the progress bar.
  * @param {string} args.progressBarColor The color of the progress bar.
  * @param {string} args.progressBarBackgroundColor The color of the progress bar background.
diff --git a/src/common/Card.js b/src/common/Card.js
index 753ce3f..33e6bb9 100644
--- a/src/common/Card.js
+++ b/src/common/Card.js
@@ -150,7 +150,9 @@ class Card {
    * @returns {string} The rendered card gradient.
    */
   renderGradient() {
-    if (typeof this.colors.bgColor !== "object") return "";
+    if (typeof this.colors.bgColor !== "object") {
+      return "";
+    }
 
     const gradients = this.colors.bgColor.slice(1);
     return typeof this.colors.bgColor === "object"
diff --git a/src/common/I18n.js b/src/common/I18n.js
index a2eccc0..bd5f29f 100644
--- a/src/common/I18n.js
+++ b/src/common/I18n.js
@@ -1,3 +1,5 @@
+const FALLBACK_LOCALE = "en";
+
 /**
  * I18n translation class.
  */
@@ -6,13 +8,12 @@ class I18n {
    * Constructor.
    *
    * @param {Object} options Options.
-   * @param {string} options.locale Locale.
+   * @param {string=} options.locale Locale.
    * @param {Object} options.translations Translations.
    */
   constructor({ locale, translations }) {
-    this.locale = locale;
+    this.locale = locale || FALLBACK_LOCALE;
     this.translations = translations;
-    this.fallbackLocale = "en";
   }
 
   /**
@@ -22,17 +23,17 @@ class I18n {
    * @returns {string} Translated string.
    */
   t(str) {
-    const locale = this.locale || this.fallbackLocale;
-
     if (!this.translations[str]) {
       throw new Error(`${str} Translation string not found`);
     }
 
-    if (!this.translations[str][locale]) {
-      throw new Error(`'${str}' translation not found for locale '${locale}'`);
+    if (!this.translations[str][this.locale]) {
+      throw new Error(
+        `'${str}' translation not found for locale '${this.locale}'`,
+      );
     }
 
-    return this.translations[str][locale];
+    return this.translations[str][this.locale];
   }
 }
 
diff --git a/src/common/createProgressNode.js b/src/common/createProgressNode.js
index 2825583..2d7303a 100644
--- a/src/common/createProgressNode.js
+++ b/src/common/createProgressNode.js
@@ -1,3 +1,5 @@
+// @ts-check
+
 import { clampValue } from "./utils.js";
 
 /**
@@ -8,7 +10,7 @@ import { clampValue } from "./utils.js";
  * @param {number} createProgressNodeParams.y Y-axis position.
  * @param {number} createProgressNodeParams.width Width of progress bar.
  * @param {string} createProgressNodeParams.color Progress color.
- * @param {string} createProgressNodeParams.progress Progress value.
+ * @param {number} createProgressNodeParams.progress Progress value.
  * @param {string} createProgressNodeParams.progressBarBackgroundColor Progress bar bg color.
  * @param {number} createProgressNodeParams.delay Delay before animation starts.
  * @returns {string} Progress node.
diff --git a/src/common/retryer.js b/src/common/retryer.js
index 49d506d..74f9a0f 100644
--- a/src/common/retryer.js
+++ b/src/common/retryer.js
@@ -1,10 +1,12 @@
 import { CustomError, logger } from "./utils.js";
 
 // Script variables.
+
+// Count the number of GitHub API tokens available.
 const PATs = Object.keys(process.env).filter((key) =>
   /PAT_\d*$/.exec(key),
 ).length;
-const RETRIES = PATs ? PATs : 7;
+const RETRIES = process.env.NODE_ENV === "test" ? 7 : PATs;
 
 /**
  * @typedef {import("axios").AxiosResponse} AxiosResponse Axios response.
@@ -20,8 +22,14 @@ const RETRIES = PATs ? PATs : 7;
  * @returns {Promise<T>} The response from the fetcher function.
  */
 const retryer = async (fetcher, variables, retries = 0) => {
+  if (!RETRIES) {
+    throw new CustomError("No GitHub API tokens found", CustomError.NO_TOKENS);
+  }
   if (retries > RETRIES) {
-    throw new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY);
+    throw new CustomError(
+      "Downtime due to GitHub API rate limiting",
+      CustomError.MAX_RETRY,
+    );
   }
   try {
     // try to fetch with the first token since RETRIES is 0 index i'm adding +1
diff --git a/src/common/utils.js b/src/common/utils.js
index 4b5dced..871c493 100644
--- a/src/common/utils.js
+++ b/src/common/utils.js
@@ -34,6 +34,51 @@ const renderError = (message, secondaryMessage = "") => {
   `;
 };
 
+/**
+ * Creates a node to display the primary programming language of the repository/gist.
+ *
+ * @param {string} langName Language name.
+ * @param {string} langColor Language color.
+ * @returns {string} Language display SVG object.
+ */
+const createLanguageNode = (langName, langColor) => {
+  return `
+    <g data-testid="primary-lang">
+      <circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
+      <text data-testid="lang-name" class="gray" x="15">${langName}</text>
+    </g>
+    `;
+};
+
+/**
+ * Creates an icon with label to display repository/gist stats like forks, stars, etc.
+ *
+ * @param {string} icon The icon to display.
+ * @param {number|string} label The label to display.
+ * @param {string} testid The testid to assign to the label.
+ * @param {number} iconSize The size of the icon.
+ * @returns {string} Icon with label SVG object.
+ */
+const iconWithLabel = (icon, label, testid, iconSize) => {
+  if (typeof label === "number" && label <= 0) {
+    return "";
+  }
+  const iconSvg = `
+      <svg
+        class="icon"
+        y="-12"
+        viewBox="0 0 16 16"
+        version="1.1"
+        width="${iconSize}"
+        height="${iconSize}"
+      >
+        ${icon}
+      </svg>
+    `;
+  const text = `<text data-testid="${testid}" class="gray">${label}</text>`;
+  return flexLayout({ items: [iconSvg, text], gap: 20 }).join("");
+};
+
 /**
  * Encode string as HTML.
  *
@@ -81,7 +126,9 @@ const isValidHexColor = (hexColor) => {
  * @returns {boolean | undefined } The parsed value.
  */
 const parseBoolean = (value) => {
-  if (typeof value === "boolean") return value;
+  if (typeof value === "boolean") {
+    return value;
+  }
 
   if (typeof value === "string") {
     if (value.toLowerCase() === "true") {
@@ -100,7 +147,9 @@ const parseBoolean = (value) => {
  * @returns {string[]} The array of strings.
  */
 const parseArray = (str) => {
-  if (!str) return [];
+  if (!str) {
+    return [];
+  }
   return str.split(",");
 };
 
@@ -114,7 +163,9 @@ const parseArray = (str) => {
  */
 const clampValue = (number, min, max) => {
   // @ts-ignore
-  if (Number.isNaN(parseInt(number))) return min;
+  if (Number.isNaN(parseInt(number))) {
+    return min;
+  }
   return Math.max(min, Math.min(number, max));
 };
 
@@ -198,12 +249,12 @@ const flexLayout = ({ items, gap, direction, sizes = [] }) => {
 /**
  * Object containing card colors.
  * @typedef {{
- *  titleColor: string | string[];
- *  iconColor: string | string[];
- *  textColor: string | string[];
+ *  titleColor: string;
+ *  iconColor: string;
+ *  textColor: string;
  *  bgColor: string | string[];
- *  borderColor: string | string[];
- *  ringColor: string | string[];
+ *  borderColor: string;
+ *  ringColor: string;
  * }} CardColors
  */
 
@@ -267,6 +318,18 @@ const getCardColors = ({
     "#" + defaultBorderColor,
   );
 
+  if (
+    typeof titleColor !== "string" ||
+    typeof textColor !== "string" ||
+    typeof ringColor !== "string" ||
+    typeof iconColor !== "string" ||
+    typeof borderColor !== "string"
+  ) {
+    throw new Error(
+      "Unexpected behavior, all colors except background should be string.",
+    );
+  }
+
   return { titleColor, iconColor, textColor, bgColor, borderColor, ringColor };
 };
 
@@ -319,7 +382,9 @@ const CONSTANTS = {
 
 const SECONDARY_ERROR_MESSAGES = {
   MAX_RETRY:
-    "Please add an env variable called PAT_1 with your github token in vercel",
+    "You can deploy own instance or wait until public will be no longer limited",
+  NO_TOKENS:
+    "Please add an env variable called PAT_1 with your GitHub API token in vercel",
   USER_NOT_FOUND: "Make sure the provided username is not an organization",
   GRAPHQL_ERROR: "Please try again later",
   WAKATIME_USER_NOT_FOUND: "Make sure you have a public WakaTime profile",
@@ -340,6 +405,7 @@ class CustomError extends Error {
   }
 
   static MAX_RETRY = "MAX_RETRY";
+  static NO_TOKENS = "NO_TOKENS";
   static USER_NOT_FOUND = "USER_NOT_FOUND";
   static GRAPHQL_ERROR = "GRAPHQL_ERROR";
   static WAKATIME_ERROR = "WAKATIME_ERROR";
@@ -446,7 +512,9 @@ const chunkArray = (arr, perChunk) => {
  * @returns {string} String with emoji parsed.
  */
 const parseEmojis = (str) => {
-  if (!str) throw new Error("[parseEmoji]: str argument not provided");
+  if (!str) {
+    throw new Error("[parseEmoji]: str argument not provided");
+  }
   return str.replace(/:\w+:/gm, (emoji) => {
     return toEmoji.get(emoji) || "";
   });
@@ -469,6 +537,8 @@ const dateDiff = (d1, d2) => {
 export {
   ERROR_CARD_LENGTH,
   renderError,
+  createLanguageNode,
+  iconWithLabel,
   encodeHTML,
   kFormatter,
   isValidHexColor,
diff --git a/src/fetchers/gist-fetcher.js b/src/fetchers/gist-fetcher.js
index 9ede43f..ebf5819 100644
--- a/src/fetchers/gist-fetcher.js
+++ b/src/fetchers/gist-fetcher.js
@@ -57,10 +57,16 @@ const fetcher = async (variables, token) => {
  * @returns {Promise<GistData>} Gist data.
  */
 const fetchGist = async (id) => {
-  if (!id) throw new MissingParamError(["id"], "/api/gist?id=GIST_ID");
+  if (!id) {
+    throw new MissingParamError(["id"], "/api/gist?id=GIST_ID");
+  }
   const res = await retryer(fetcher, { gistName: id });
-  if (res.data.errors) throw new Error(res.data.errors[0].message);
-  if (!res.data.data.viewer.gist) throw new Error("Gist not found");
+  if (res.data.errors) {
+    throw new Error(res.data.errors[0].message);
+  }
+  if (!res.data.data.viewer.gist) {
+    throw new Error("Gist not found");
+  }
   const data = res.data.data.viewer.gist;
   return {
     name: data.files[Object.keys(data.files)[0]].name,
diff --git a/src/fetchers/repo-fetcher.js b/src/fetchers/repo-fetcher.js
index 98e0a72..6438f88 100644
--- a/src/fetchers/repo-fetcher.js
+++ b/src/fetchers/repo-fetcher.js
@@ -73,8 +73,12 @@ const fetchRepo = async (username, reponame) => {
   if (!username && !reponame) {
     throw new MissingParamError(["username", "repo"], urlExample);
   }
-  if (!username) throw new MissingParamError(["username"], urlExample);
-  if (!reponame) throw new MissingParamError(["repo"], urlExample);
+  if (!username) {
+    throw new MissingParamError(["username"], urlExample);
+  }
+  if (!reponame) {
+    throw new MissingParamError(["repo"], urlExample);
+  }
 
   let res = await retryer(fetcher, { login: username, repo: reponame });
 
diff --git a/src/fetchers/stats-fetcher.js b/src/fetchers/stats-fetcher.js
index ac51d49..dd62b46 100644
--- a/src/fetchers/stats-fetcher.js
+++ b/src/fetchers/stats-fetcher.js
@@ -116,7 +116,9 @@ const statsFetcher = async (username) => {
   while (hasNextPage) {
     const variables = { login: username, first: 100, after: endCursor };
     let res = await retryer(fetcher, variables);
-    if (res.data.errors) return res;
+    if (res.data.errors) {
+      return res;
+    }
 
     // Store stats data.
     const repoNodes = res.data.data.user.repositories.nodes;
@@ -199,7 +201,9 @@ const fetchStats = async (
   include_all_commits = false,
   exclude_repo = [],
 ) => {
-  if (!username) throw new MissingParamError(["username"]);
+  if (!username) {
+    throw new MissingParamError(["username"]);
+  }
 
   const stats = {
     name: "",
diff --git a/src/fetchers/top-languages-fetcher.js b/src/fetchers/top-languages-fetcher.js
index 26e99e6..faf4bc9 100644
--- a/src/fetchers/top-languages-fetcher.js
+++ b/src/fetchers/top-languages-fetcher.js
@@ -71,7 +71,9 @@ const fetchTopLanguages = async (
   size_weight = 1,
   count_weight = 0,
 ) => {
-  if (!username) throw new MissingParamError(["username"]);
+  if (!username) {
+    throw new MissingParamError(["username"]);
+  }
 
   const res = await retryer(fetcher, { login: username });
 
diff --git a/src/fetchers/wakatime-fetcher.js b/src/fetchers/wakatime-fetcher.js
index 2a2e039..f69d6ae 100644
--- a/src/fetchers/wakatime-fetcher.js
+++ b/src/fetchers/wakatime-fetcher.js
@@ -8,7 +8,9 @@ import { CustomError, MissingParamError } from "../common/utils.js";
  * @returns {Promise<WakaTimeData>} WakaTime data response.
  */
 const fetchWakatimeStats = async ({ username, api_domain }) => {
-  if (!username) throw new MissingParamError(["username"]);
+  if (!username) {
+    throw new MissingParamError(["username"]);
+  }
 
   try {
     const { data } = await axios.get(
diff --git a/src/getStyles.js b/src/getStyles.js
index 1bfdc7b..c621ba1 100644
--- a/src/getStyles.js
+++ b/src/getStyles.js
@@ -10,8 +10,12 @@ const calculateCircleProgress = (value) => {
   const radius = 40;
   const c = Math.PI * (radius * 2);
 
-  if (value < 0) value = 0;
-  if (value > 100) value = 100;
+  if (value < 0) {
+    value = 0;
+  }
+  if (value > 100) {
+    value = 100;
+  }
 
   return ((100 - value) / 100) * c;
 };


Test patch:
diff --git a/tests/fetchStats.test.js b/tests/fetchStats.test.js
index 32e302e..ac4d0a3 100644
--- a/tests/fetchStats.test.js
+++ b/tests/fetchStats.test.js
@@ -237,6 +237,37 @@ describe("Test fetchStats", () => {
     });
   });
 
+  it("should return 0 commits when all_commits true and API returns error", async () => {
+    mock
+      .onGet("https://api.github.com/search/commits?q=author:anuraghazra")
+      .reply(200, { error: "Some test error message" });
+
+    let stats = await fetchStats("anuraghazra", true);
+    expect(stats).toStrictEqual({
+      contributedTo: 61,
+      name: "Anurag Hazra",
+      totalCommits: 0,
+      totalIssues: 200,
+      totalPRs: 300,
+      totalPRsMerged: 240,
+      mergedPRsPercentage: 80,
+      totalReviews: 50,
+      totalStars: 300,
+      totalDiscussionsStarted: 10,
+      totalDiscussionsAnswered: 40,
+      rank: calculateRank({
+        all_commits: true,
+        commits: 0,
+        prs: 300,
+        reviews: 50,
+        issues: 200,
+        repos: 5,
+        stars: 300,
+        followers: 100,
+      }),
+    });
+  });
+
   it("should exclude stars of the `test-repo-1` repository", async () => {
     mock
       .onGet("https://api.github.com/search/commits?q=author:anuraghazra")
diff --git a/tests/renderTopLanguagesCard.test.js b/tests/renderTopLanguagesCard.test.js
index 0506d96..6b7ef62 100644
--- a/tests/renderTopLanguagesCard.test.js
+++ b/tests/renderTopLanguagesCard.test.js
@@ -70,7 +70,9 @@ const langPercentFromDonutLayoutSvg = (d, centerX, centerY) => {
     cartesianToPolar(centerX, centerY, dTmp[0], dTmp[1]).angleInDegrees + 90;
   let startAngle =
     cartesianToPolar(centerX, centerY, dTmp[7], dTmp[8]).angleInDegrees + 90;
-  if (startAngle > endAngle) startAngle -= 360;
+  if (startAngle > endAngle) {
+    startAngle -= 360;
+  }
   return (endAngle - startAngle) / 3.6;
 };
 
diff --git a/tests/retryer.test.js b/tests/retryer.test.js
index 2229bbb..257a70d 100644
--- a/tests/retryer.test.js
+++ b/tests/retryer.test.js
@@ -40,12 +40,12 @@ describe("Test Retryer", () => {
     expect(res).toStrictEqual({ data: "ok" });
   });
 
-  it("retryer should throw error if maximum retries reached", async () => {
+  it("retryer should throw specific error if maximum retries reached", async () => {
     try {
       await retryer(fetcherFail, {});
     } catch (err) {
       expect(fetcherFail).toBeCalledTimes(8);
-      expect(err.message).toBe("Maximum retries exceeded");
+      expect(err.message).toBe("Downtime due to GitHub API rate limiting");
     }
   });
 });