diff --git a/README.md b/README.md index 76f6153a..15e2f565 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,8 @@ If the `theme` parameter is specified, any color customizations specified will b | `currStreakLabel` | Current streak label | **hex code** without `#` or **css color** | | `sideLabels` | Total and longest streak labels | **hex code** without `#` or **css color** | | `dates` | Date range text color | **hex code** without `#` or **css color** | -| `date_format` | Date format (Default: `M j[, Y]`) | See note below on [📅 Date Formats](#-date-formats) | -| `locale` | Locale to use for labels (Default: `en`) | ISO 639-1 code - See [🗪 Locales](#-locales) | +| `date_format` | Date format pattern or empty for locale format | See note below on [📅 Date Formats](#-date-formats) | +| `locale` | Locale for labels and numbers (Default: `en`) | ISO 639-1 code - See [🗪 Locales](#-locales) | | `type` | Output format (Default: `svg`) | Current options: `svg`, `png` or `json` | | `mode` | Streak mode (Default: `daily`) | `daily` (contribute daily) or `weekly` (contribute once per Sun-Sat week) | @@ -83,10 +83,12 @@ To enable a theme, append `&theme=` followed by the theme name to the end of the ### 🗪 Locales +The following are the locales that have labels translated in Streak Stats. The `locale` query parameter accepts any ISO language or locale code, see [here](https://gist.github.com/DenverCoder1/f61147ba26bfcf7c3bf605af7d3382d5) for a list of valid locales. The locale provided will be used for the date format and number format even if translations are not yet available. + -
en - English
English 100%
ar - العربية
العربية 100%
bg - български
български 100%
bn - বাংলা
বাংলা 100%
es - español
español 100%
fa - فارسی
فارسی 100%
fr - français
français 100%
he - עברית
עברית 100%
hi - हिन्दी
हिन्दी 100%
ja - 日本語
日本語 100%
ko - 한국어
한국어 100%
mr - मराठी
मराठी 100%
pl - polski
polski 100%
ps - پښتو
پښتو 100%
pt_BR - português (Brasil)
português (Brasil) 100%
ru - русский
русский 100%
uk - українська
українська 100%
yo - Èdè Yorùbá
Èdè Yorùbá 100%
zh_Hans - 中文(简体)
中文(简体) 100%
zh_Hant - 中文(繁體)
中文(繁體) 100%
da - dansk
dansk 67%
de - Deutsch
Deutsch 67%
id - Indonesia
Indonesia 67%
it - italiano
italiano 67%
kn - ಕನ್ನಡ
ಕನ್ನಡ 67%
nl - Nederlands
Nederlands 67%
ta - தமிழ்
தமிழ் 67%
tr - Türkçe
Türkçe 67%
vi - Tiếng Việt
Tiếng Việt 67%
+
en - English
English 100%
ar - العربية
العربية 100%
bg - български
български 100%
bn - বাংলা
বাংলা 100%
da - dansk
dansk 100%
de - Deutsch
Deutsch 100%
es - español
español 100%
fa - فارسی
فارسی 100%
fr - français
français 100%
he - עברית
עברית 100%
hi - हिन्दी
हिन्दी 100%
ht - Haitian Creole
Haitian Creole 100%
id - Indonesia
Indonesia 100%
it - italiano
italiano 100%
ja - 日本語
日本語 100%
kn - ಕನ್ನಡ
ಕನ್ನಡ 100%
ko - 한국어
한국어 100%
mr - मराठी
मराठी 100%
nl - Nederlands
Nederlands 100%
pl - polski
polski 100%
ps - پښتو
پښتو 100%
pt_BR - português (Brasil)
português (Brasil) 100%
ru - русский
русский 100%
uk - українська
українська 100%
ur_PK - اردو (پاکستان)
اردو (پاکستان) 100%
vi - Tiếng Việt
Tiếng Việt 100%
yo - Èdè Yorùbá
Èdè Yorùbá 100%
zh_Hans - 中文(简体)
中文(简体) 100%
zh_Hant - 中文(繁體)
中文(繁體) 100%
ta - தமிழ்
தமிழ் 67%
tr - Türkçe
Türkçe 67%
@@ -94,6 +96,8 @@ To enable a theme, append `&theme=` followed by the theme name to the end of the ### 📅 Date Formats +If `date_format` is not provided or is empty, the PHP Intl library is used to determine the date format based on the locale specified in the `locale` query parameter. + A custom date format can be specified by passing a string to the `date_format` parameter. The required format is to use format string characters from [PHP's date function](https://www.php.net/manual/en/datetime.format.php) with brackets around the part representing the year. @@ -191,6 +195,7 @@ PNG mode is also not supported since Inkscape will not be installed. 9. Scroll to the bottom and click **"Generate token"** 10. Visit the [Vercel dashboard](https://vercel.com/dashboard) and select your project, then click **"Settings"**, then **"Environment Variables"**. 11. Add a new variable with the key `TOKEN` and the value as your token from step 9 and click "Save". +12. For the environment variable to be available, you will need to redeploy the app. Run `vercel --prod` to deploy to production. ![image](https://user-images.githubusercontent.com/20955511/209588756-8bf5b0cd-9aa6-41e8-909c-97bf41e525b3.png) diff --git a/composer.json b/composer.json index bf460e42..416268a5 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "stats" ], "license": "MIT", - "version": "0.26.0", + "version": "0.27.0", "homepage": "https://github.com/DenverCoder1/github-readme-streak-stats", "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index e7195848..3093eb6c 100644 --- a/composer.lock +++ b/composer.lock @@ -479,30 +479,30 @@ "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.5.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^11", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -529,7 +529,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -545,7 +545,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:15:36+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "myclabs/deep-copy", @@ -608,16 +608,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.2", + "version": "v4.15.3", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc" + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", "shasum": "" }, "require": { @@ -658,9 +658,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" }, - "time": "2022-11-12T15:38:23+00:00" + "time": "2023-01-16T22:05:37+00:00" }, { "name": "phar-io/manifest", @@ -1093,20 +1093,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.27", + "version": "9.5.28", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38" + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38", - "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", + "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -1175,7 +1175,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.27" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" }, "funding": [ { @@ -1191,7 +1191,7 @@ "type": "tidelift" } ], - "time": "2022-12-09T07:31:23+00:00" + "time": "2023-01-14T12:32:24+00:00" }, { "name": "sebastian/cli-parser", diff --git a/src/demo/css/style.css b/src/demo/css/style.css index 014312c9..13beac15 100644 --- a/src/demo/css/style.css +++ b/src/demo/css/style.css @@ -121,6 +121,7 @@ h2 { .btn:disabled { background: var(--blue-transparent); box-shadow: none; + cursor: not-allowed; } .parameters { @@ -199,6 +200,7 @@ input:focus:invalid { .advanced summary { padding: 6px; + cursor: pointer; } .advanced .parameters { @@ -282,9 +284,10 @@ input:focus:invalid { .btn.tooltip:after { content: ""; position: absolute; - transform: translateY(-27px); + transform: translateY(-25px); border-style: solid; border-color: #4a4a4afa transparent transparent transparent; + border-width: 5px; pointer-events: none; opacity: 0; } @@ -302,7 +305,7 @@ input:focus:invalid { content: "You must first input a valid username."; } -textarea#exportedPhp { +textarea#exported-php { margin-top: 10px; width: 100%; resize: vertical; diff --git a/src/demo/index.php b/src/demo/index.php index bf9ab95e..3726a478 100644 --- a/src/demo/index.php +++ b/src/demo/index.php @@ -102,13 +102,13 @@ function gtag() { - - + - + - @@ -146,13 +146,14 @@ function gtag() { - + - - + + + - + @@ -192,4 +193,4 @@ function gtag() { - \ No newline at end of file + diff --git a/src/demo/js/script.js b/src/demo/js/script.js index 9e356e0f..f9d49e89 100644 --- a/src/demo/js/script.js +++ b/src/demo/js/script.js @@ -37,15 +37,17 @@ const preview = { // disable copy button if username is invalid const copyButton = document.querySelector(".copy-button"); copyButton.disabled = Boolean(document.querySelector("#user:invalid") || !document.querySelector("#user").value); + // disable clear button if no added advanced options + const clearButton = document.querySelector("#clear-button"); + clearButton.disabled = !document.querySelectorAll(".minus").length; }, /** * Add a property in the advanced section * @param {string} property - the name of the property, selected element is used if not provided * @param {string} value - the value to set the property to - * @returns {false} false to prevent the default action */ - addProperty(property, value = "#DD2727FF") { + addProperty(property, value = "#EB5454FF") { const selectElement = document.querySelector("#properties"); // if no property passed, get the currently selected property const propertyName = property || selectElement.value; @@ -80,6 +82,7 @@ const preview = { const minus = document.createElement("button"); minus.className = "minus btn"; minus.setAttribute("onclick", "return preview.removeProperty(this.getAttribute('data-property'));"); + minus.setAttribute("type", "button"); minus.innerText = "−"; minus.setAttribute("data-property", propertyName); // add elements @@ -97,13 +100,11 @@ const preview = { // update and exit this.update(); } - return false; }, /** * Remove a property from the advanced section * @param {string} property - the name of the property to remove - * @returns {false} false to prevent the default action */ removeProperty(property) { const parent = document.querySelector(".advanced .parameters"); @@ -116,11 +117,24 @@ const preview = { option.disabled = false; // update and exit this.update(); - return false; }, /** - * Create a key-value mapping of ids to values from all elements in a Node list + * Removes all properties from the advanced section + */ + removeAllProperties() { + const parent = document.querySelector(".advanced .parameters"); + const activeProperties = parent.querySelectorAll("[data-property]"); + // select active and unique property names + const propertyNames = Array.prototype.map + .call(activeProperties, (prop) => prop.getAttribute("data-property")) + .filter((value, index, self) => self.indexOf(value) === index); + // remove each active property name + propertyNames.forEach((prop) => this.removeProperty(prop)); + }, + + /** + * Create a key-value mapping of names to values from all elements in a Node list * @param {NodeList} elements - the elements to get the values from * @returns {Object} the key-value mapping */ @@ -136,7 +150,7 @@ const preview = { value = value.replace(/[Ff]{2}$/, ""); } } - obj[next.id] = value; + obj[next.name] = value; return obj; }, {}); }, @@ -159,7 +173,7 @@ const preview = { .join("\n"); const output = `[\n${mappings}\n]`; // set the textarea value to the output - const textarea = document.getElementById("exportedPhp"); + const textarea = document.getElementById("exported-php"); textarea.value = output; textarea.hidden = false; }, @@ -172,7 +186,7 @@ const preview = { checkColor(color, input) { if (color.length === 9 && color.slice(-2) === "FF") { // if color has hex alpha value -> remove it - document.getElementById(input).value = color.slice(0, -2); + document.querySelector(`[name="${input}"]`).value = color.slice(0, -2); } }, @@ -220,17 +234,20 @@ const tooltip = { }, }; -// refresh preview on interactions with the page -document.addEventListener("keyup", () => preview.update(), false); -document.addEventListener("click", () => preview.update(), false); - // when the page loads window.addEventListener( "load", () => { + // refresh preview on interactions with the page + const refresh = () => preview.update(); + document.addEventListener("keyup", refresh, false); + document.addEventListener("click", refresh, false); + [...document.querySelectorAll("select:not(#properties)")].forEach((element) => { + element.addEventListener("change", refresh, false); + }); // set input boxes to match URL parameters new URLSearchParams(window.location.search).forEach((val, key) => { - const paramInput = document.querySelector(`#${key}`); + const paramInput = document.querySelector(`[name="${key}"]`); if (paramInput) { // set parameter value paramInput.value = val; diff --git a/src/stats.php b/src/stats.php index 73bac082..d6551652 100644 --- a/src/stats.php +++ b/src/stats.php @@ -83,6 +83,7 @@ function executeContributionGraphRequests(string $user, array $years): array removeGitHubToken($tokens[$year]); } error_log("First attempt to decode response for $user's $year contributions failed. $message"); + error_log("Contents: $contents"); // retry request $query = buildContributionGraphQuery($user, $year); $token = getGitHubToken(); @@ -96,6 +97,7 @@ function executeContributionGraphRequests(string $user, array $years): array removeGitHubToken($token); } error_log("Failed to decode response for $user's $year contributions after 2 attempts. $message"); + error_log("Contents: $contents"); continue; } } @@ -120,7 +122,11 @@ function getContributionGraphs(string $user): array // get the list of years the user has contributed and the current year's contribution graph $currentYear = intval(date("Y")); $responses = executeContributionGraphRequests($user, [$currentYear]); - $contributionYears = $responses[$currentYear]->data->user->contributionsCollection->contributionYears; + $contributionYears = $responses[$currentYear]->data->user->contributionsCollection->contributionYears ?? []; + // if there are no contribution years, an API error must have occurred + if (empty($contributionYears)) { + throw new AssertionError("Failed to retrieve contributions. This is likely a GitHub API issue.", 500); + } // remove the current year from the list since it's already been fetched $contributionYears = array_filter($contributionYears, function ($year) use ($currentYear) { return $year !== $currentYear; diff --git a/src/translations.php b/src/translations.php index 26ec3f2d..7b55eccf 100644 --- a/src/translations.php +++ b/src/translations.php @@ -68,12 +68,16 @@ "Total Contributions" => "Totalt Antal Bidrag", "Current Streak" => "Nuværende i Træk", "Longest Streak" => "Længst i Træk", - "Present" => "I dag", + "Week Streak" => "Uger i Træk", + "Longest Week Streak" => "Mest Uger i Træk", + "Present" => "I Dag", ], "de" => [ "Total Contributions" => "Gesamte Beiträge", "Current Streak" => "Aktuelle Serie", "Longest Streak" => "Längste Serie", + "Week Streak" => "Wochenserie", + "Longest Week Streak" => "Längste Wochenserie", "Present" => "Heute", ], "es" => [ @@ -118,16 +122,28 @@ "Longest Week Streak" => "दीर्घ साप्ताहिक योगदान", "Present" => "आज तक", ], + "ht" => [ + "Total Contributions" => "kontribisyon total", + "Current Streak" => "tras aktyèl", + "Longest Streak" => "tras ki pi long", + "Week Streak" => "tras semèn", + "Longest Week Streak" => "pi long tras semèn", + "Present" => "Prezan", + ], "id" => [ "Total Contributions" => "Total Kontribusi", "Current Streak" => "Aksi Saat Ini", "Longest Streak" => "Aksi Terpanjang", + "Week Streak" => "Aksi Mingguan", + "Longest Week Streak" => "Aksi Mingguan Terpanjang", "Present" => "Sekarang", ], "it" => [ - "Total Contributions" => "Tutti i contributi", - "Current Streak" => "Serie corrente", - "Longest Streak" => "Serie più lunga", + "Total Contributions" => "Totale dei Contributi", + "Current Streak" => "Serie Corrente", + "Longest Streak" => "Serie più Lunga", + "Week Streak" => "Serie Settimanale", + "Longest Week Streak" => "Serie Settimanale più Lunga", "Present" => "Presente", ], "ja" => [ @@ -142,7 +158,9 @@ "kn" => [ "Total Contributions" => "ಒಟ್ಟು ಕೊಡುಗೆ", "Current Streak" => "ಪ್ರಸ್ತುತ ಸ್ಟ್ರೀಕ್", - "Longest Streak" => "ದೊಡ್ಡ ಸ್ಟ್ರೀಕ್", + "Longest Streak" => "ಅತ್ಯಧಿಕ ಸ್ಟ್ರೀಕ್", + "Week Streak" => "ವಾರದ ಸ್ಟ್ರೀಕ್", + "Longest Week Streak" => "ಅತ್ಯಧಿಕ ವಾರದ ಸ್ಟ್ರೀಕ್", "Present" => "ಪ್ರಸ್ತುತ", ], "ko" => [ @@ -165,6 +183,8 @@ "Total Contributions" => "Totale Bijdrage", "Current Streak" => "Huidige Serie", "Longest Streak" => "Langste Serie", + "Week Streak" => "Week Serie", + "Longest Week Streak" => "Langste Week Serie", "Present" => "Vandaag", ], "pl" => [ @@ -219,10 +239,21 @@ "Longest Week Streak" => "Найбільша к-сть тижнів", "Present" => "Наразі", ], + "ur_PK" => [ + "rtl" => true, + "Total Contributions" => "کل حصہ داری", + "Current Streak" => "موجودہ تسلسل", + "Longest Streak" => "طویل ترین تسلسل", + "Week Streak" => "ہفتہ وار تسلسل", + "Longest Week Streak" => "طویل ترین ہفتہ وار تسلسل", + "Present" => "حاظر", + ], "vi" => [ "Total Contributions" => "Tổng số đóng góp", "Current Streak" => "Chuỗi đóng góp\nhiện tại", "Longest Streak" => "Chuỗi đóng góp lớn nhất", + "Week Streak" => "Chuỗi tuần", + "Longest Week Streak" => "Chuỗi tuần lớn nhất", "Present" => "Hiện tại", ], "yo" => [