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" => [ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy