diff --git a/api/top-langs.js b/api/top-langs.js index bb6338b763abe..6d0e089d1c384 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -41,7 +41,7 @@ module.exports = async (req, res) => { } if (locale && !isLocaleAvailable(locale)) { - return res.send(renderError("Something went wrong", "Language not found")); + return res.send(renderError("Something went wrong", "Locale not found")); } try { diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index 36b4b0cfe9014..f281b12567239 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -4,6 +4,13 @@ const { langCardLocales } = require("../translations"); const { createProgressNode } = require("../common/createProgressNode"); const { clampValue, getCardColors, flexLayout } = require("../common/utils"); +const DEFAULT_CARD_WIDTH = 300; +const DEFAULT_LANGS_COUNT = 5; +const DEFAULT_LANG_COLOR = "#858585"; +const CARD_PADDING = 25; + +const lowercaseTrim = (name) => name.toLowerCase().trim(); + const createProgressTextNode = ({ width, color, name, progress }) => { const paddingRight = 95; const progressTextX = width - paddingRight + 10; @@ -58,35 +65,99 @@ const createLanguageTextNode = ({ langs, totalSize, x, y }) => { }); }; -const lowercaseTrim = (name) => name.toLowerCase().trim(); +/** + * + * @param {any[]} langs + * @param {number} width + * @param {number} totalLanguageSize + * @returns {string} + */ +const renderNormalLayout = (langs, width, totalLanguageSize) => { + return flexLayout({ + items: langs.map((lang) => { + return createProgressTextNode({ + width: width, + name: lang.name, + color: lang.color || DEFAULT_LANG_COLOR, + progress: ((lang.size / totalLanguageSize) * 100).toFixed(2), + }); + }), + gap: 40, + direction: "column", + }).join(""); +}; -const renderTopLanguages = (topLangs, options = {}) => { - const { - hide_title, - hide_border, - card_width, - title_color, - text_color, - bg_color, - hide, - theme, - layout, - custom_title, - locale, - langs_count = 5, - border_radius, - border_color, - } = options; +/** + * + * @param {any[]} langs + * @param {number} width + * @param {number} totalLanguageSize + * @returns {string} + */ +const renderCompactLayout = (langs, width, totalLanguageSize) => { + const paddingRight = 50; + const offsetWidth = width - paddingRight; + // progressOffset holds the previous language's width and used to offset the next language + // so that we can stack them one after another, like this: [--][----][---] + let progressOffset = 0; + const compactProgressBar = langs + .map((lang) => { + const percentage = parseFloat( + ((lang.size / totalLanguageSize) * offsetWidth).toFixed(2), + ); - const i18n = new I18n({ - locale, - translations: langCardLocales, - }); + const progress = percentage < 10 ? percentage + 10 : percentage; + + const output = ` + + `; + progressOffset += percentage; + return output; + }) + .join(""); + + return ` + + + + ${compactProgressBar} + ${createLanguageTextNode({ + x: 0, + y: 25, + langs, + totalSize: totalLanguageSize, + }).join("")} + `; +}; + +/** + * @param {number} totalLangs + * @returns {number} + */ +const calculateCompactLayoutHeight = (totalLangs) => { + return 90 + Math.round(totalLangs / 2) * 25; +}; +/** + * @param {number} totalLangs + * @returns {number} + */ +const calculateNormalLayoutHeight = (totalLangs) => { + return 45 + (totalLangs + 1) * 40; +}; + +const useLanguages = (topLangs, hide, langs_count) => { let langs = Object.values(topLangs); let langsToHide = {}; - - langsCount = clampValue(parseInt(langs_count), 1, 10); + let langsCount = clampValue(parseInt(langs_count), 1, 10); // populate langsToHide map for quick lookup // while filtering out @@ -104,110 +175,80 @@ const renderTopLanguages = (topLangs, options = {}) => { }) .slice(0, langsCount); - const totalLanguageSize = langs.reduce((acc, curr) => { - return acc + curr.size; - }, 0); + const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0); - // returns theme based colors with proper overrides and defaults - const { titleColor, textColor, bgColor, borderColor } = getCardColors({ + return { langs, totalLanguageSize }; +}; + +const renderTopLanguages = (topLangs, options = {}) => { + const { + hide_title, + hide_border, + card_width, title_color, text_color, bg_color, - border_color, + hide, theme, + layout, + custom_title, + locale, + langs_count = DEFAULT_LANGS_COUNT, + border_radius, + border_color, + } = options; + + const i18n = new I18n({ + locale, + translations: langCardLocales, }); - let width = isNaN(card_width) ? 300 : card_width; - let height = 45 + (langs.length + 1) * 40; + const { langs, totalLanguageSize } = useLanguages( + topLangs, + hide, + langs_count, + ); - let finalLayout = ""; + let width = isNaN(card_width) ? DEFAULT_CARD_WIDTH : card_width; + let height = calculateNormalLayoutHeight(langs.length); - // RENDER COMPACT LAYOUT + let finalLayout = ""; if (layout === "compact") { - width = width + 50; - height = 90 + Math.round(langs.length / 2) * 25; - - // progressOffset holds the previous language's width and used to offset the next language - // so that we can stack them one after another, like this: [--][----][---] - let progressOffset = 0; - const compactProgressBar = langs - .map((lang) => { - const percentage = ( - (lang.size / totalLanguageSize) * - (width - 50) - ).toFixed(2); - - const progress = - percentage < 10 ? parseFloat(percentage) + 10 : percentage; - - const output = ` - - `; - progressOffset += parseFloat(percentage); - return output; - }) - .join(""); - - finalLayout = ` - - - - ${compactProgressBar} - ${createLanguageTextNode({ - x: 0, - y: 25, - langs, - totalSize: totalLanguageSize, - }).join("")} - `; + width = width + 50; // padding + height = calculateCompactLayoutHeight(langs.length); + + finalLayout = renderCompactLayout(langs, width, totalLanguageSize); } else { - finalLayout = flexLayout({ - items: langs.map((lang) => { - return createProgressTextNode({ - width: width, - name: lang.name, - color: lang.color || "#858585", - progress: ((lang.size / totalLanguageSize) * 100).toFixed(2), - }); - }), - gap: 40, - direction: "column", - }).join(""); + finalLayout = renderNormalLayout(langs, width, totalLanguageSize); } + // returns theme based colors with proper overrides and defaults + const colors = getCardColors({ + title_color, + text_color, + bg_color, + border_color, + theme, + }); + const card = new Card({ customTitle: custom_title, defaultTitle: i18n.t("langcard.title"), width, height, border_radius, - colors: { - titleColor, - textColor, - bgColor, - borderColor, - }, + colors, }); card.disableAnimations(); card.setHideBorder(hide_border); card.setHideTitle(hide_title); - card.setCSS(` - .lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} } - `); + card.setCSS( + `.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} }`, + ); return card.render(` - + ${finalLayout} `); diff --git a/src/common/Card.js b/src/common/Card.js index 3b5686e668805..520560f94d24d 100644 --- a/src/common/Card.js +++ b/src/common/Card.js @@ -94,7 +94,7 @@ class Card { } 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/translations.js b/src/translations.js index 490dc5603cb2d..7f3a0f1f7dff5 100644 --- a/src/translations.js +++ b/src/translations.js @@ -9,6 +9,7 @@ const statCardLocales = ({ name, apostrophe }) => { cs: `GitHub statistiky uživatele ${encodedName}`, de: `${encodedName + apostrophe} GitHub-Statistiken`, en: `${encodedName}'${apostrophe} GitHub Stats`, + bn: `${encodedName} এর GitHub পরিসংখ্যান`, es: `Estadísticas de GitHub de ${encodedName}`, fr: `Statistiques GitHub de ${encodedName}`, hu: `${encodedName} GitHub statisztika`, @@ -34,6 +35,7 @@ const statCardLocales = ({ name, apostrophe }) => { cs: "Celkem hvězd", de: "Sterne Insgesamt", en: "Total Stars", + bn: "সর্বমোট Stars", es: "Estrellas totales", fr: "Total d'étoiles", hu: "Csillagok", @@ -59,6 +61,7 @@ const statCardLocales = ({ name, apostrophe }) => { cs: "Celkem commitů", de: "Anzahl Commits", en: "Total Commits", + bn: "সর্বমোট Commits", es: "Commits totales", fr: "Total des validations", hu: "Összes commit", @@ -84,6 +87,7 @@ const statCardLocales = ({ name, apostrophe }) => { cs: "Celkem PRs", de: "PRs Insgesamt", en: "Total PRs", + bn: "সর্বমোট PRs", es: "PRs totales", fr: "Total des PR", hu: "Összes PR", @@ -109,6 +113,7 @@ const statCardLocales = ({ name, apostrophe }) => { cs: "Celkem problémů", de: "Anzahl Issues", en: "Total Issues", + bn: "সর্বমোট Issues", es: "Issues totales", fr: "Nombre total d'incidents", hu: "Összes hibajegy", @@ -134,6 +139,7 @@ const statCardLocales = ({ name, apostrophe }) => { cs: "Přispěl k", de: "Beigetragen zu", en: "Contributed to", + bn: "অবদান রেখেছেন", es: "Contribuciones en", fr: "Contribué à", hu: "Hozzájárulások", diff --git a/tests/__snapshots__/renderWakatimeCard.test.js.snap b/tests/__snapshots__/renderWakatimeCard.test.js.snap index 4c478d5881cd2..c46f950c40dfe 100644 --- a/tests/__snapshots__/renderWakatimeCard.test.js.snap +++ b/tests/__snapshots__/renderWakatimeCard.test.js.snap @@ -61,7 +61,7 @@ exports[`Test Render Wakatime Card should render correctly 1`] = ` - undefined + - undefined + { ); expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute( "width", - "120.00", + "120", ); expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( @@ -206,7 +206,7 @@ describe("Test renderTopLanguages", () => { ); expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute( "width", - "120.00", + "120", ); expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent( @@ -214,7 +214,7 @@ describe("Test renderTopLanguages", () => { ); expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute( "width", - "60.00", + "60", ); }); @@ -228,25 +228,28 @@ describe("Test renderTopLanguages", () => { it("should render without rounding", () => { document.body.innerHTML = renderTopLanguages(langs, { border_radius: "0" }); expect(document.querySelector("rect")).toHaveAttribute("rx", "0"); - document.body.innerHTML = renderTopLanguages(langs, { }); + document.body.innerHTML = renderTopLanguages(langs, {}); expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5"); }); it("should render langs with specified langs_count", async () => { options = { - langs_count: 1 - } + langs_count: 1, + }; document.body.innerHTML = renderTopLanguages(langs, { ...options }); - expect(queryAllByTestId(document.body, "lang-name").length).toBe(options.langs_count) + expect(queryAllByTestId(document.body, "lang-name").length).toBe( + options.langs_count, + ); }); it("should render langs with specified langs_count even when hide is set", async () => { options = { hide: ["HTML"], - langs_count: 2 - } + langs_count: 2, + }; document.body.innerHTML = renderTopLanguages(langs, { ...options }); - expect(queryAllByTestId(document.body, "lang-name").length).toBe(options.langs_count) + expect(queryAllByTestId(document.body, "lang-name").length).toBe( + options.langs_count, + ); }); - });