From e9b7ac82b7d96fc190068c507c58f64947886ade Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Mon, 12 Jan 2026 11:37:09 +0100 Subject: [PATCH 1/4] chore: add a test case so that a substantative placeholder changes are appant to contributors (#6681) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/backend-test/check-translations.test.js | 73 +++++++++++++++++--- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/test/backend-test/check-translations.test.js b/test/backend-test/check-translations.test.js index f5cf9593e..6b60a5fd6 100644 --- a/test/backend-test/check-translations.test.js +++ b/test/backend-test/check-translations.test.js @@ -1,16 +1,16 @@ const { describe, it } = require("node:test"); const assert = require("node:assert"); -const fs = require("fs"); +const fs = require("fs/promises"); const path = require("path"); /** * Recursively walks a directory and yields file paths. * @param {string} dir The directory to walk. * @yields {string} The path to a file. - * @returns {Generator} A generator that yields file paths. + * @returns {AsyncGenerator} A generator that yields file paths. */ -function* walk(dir) { - const files = fs.readdirSync(dir, { withFileTypes: true }); +async function* walk(dir) { + const files = await fs.readdir(dir, { withFileTypes: true }); for (const file of files) { if (file.isDirectory()) { yield* walk(path.join(dir, file.name)); @@ -20,6 +20,29 @@ function* walk(dir) { } } +const UPSTREAM_EN_JSON = "https://raw.githubusercontent.com/louislam/uptime-kuma/refs/heads/master/src/lang/en.json"; + +/** + * Extract `{placeholders}` from a translation string. + * @param {string} value The translation string to extract placeholders from. + * @returns {Set} A set of placeholder names. + */ +function extractParams(value) { + if (typeof value !== "string") { + return new Set(); + } + + const regex = /\{([^}]+)\}/g; + const params = new Set(); + + let match; + while ((match = regex.exec(value)) !== null) { + params.add(match[1]); + } + + return params; +} + /** * Fallback to get start/end indices of a key within a line. * @param {string} line - Line of text to search in. @@ -35,8 +58,8 @@ function getStartEnd(line, key) { } describe("Check Translations", () => { - it("should not have missing translation keys", () => { - const enTranslations = JSON.parse(fs.readFileSync("src/lang/en.json", "utf-8")); + it("should not have missing translation keys", async () => { + const enTranslations = JSON.parse(await fs.readFile("src/lang/en.json", "utf-8")); // this is a resonably crude check, you can get around this trivially /// this check is just to save on maintainer energy to explain this on every review ^^ @@ -50,9 +73,9 @@ describe("Check Translations", () => { const roots = ["src", "server"]; for (const root of roots) { - for (const filePath of walk(root)) { + for await (const filePath of walk(root)) { if (filePath.endsWith(".vue") || filePath.endsWith(".js")) { - const lines = fs.readFileSync(filePath, "utf-8").split("\n"); + const lines = (await fs.readFile(filePath, "utf-8")).split("\n"); lines.forEach((line, lineNum) => { let match; // front-end style keys ($t / i18n-t) @@ -112,4 +135,38 @@ describe("Check Translations", () => { assert.fail(report); } }); + + it("en.json translations must not change placeholder parameters", async () => { + // Load local reference (the one translators are synced against) + const enTranslations = JSON.parse(await fs.readFile("src/lang/en.json", "utf-8")); + + // Fetch upstream version + const res = await fetch(UPSTREAM_EN_JSON); + assert.equal(res.ok, true, "Failed to fetch upstream en.json"); + + const upstreamEn = await res.json(); + + for (const [key, upstreamValue] of Object.entries(upstreamEn)) { + if (!(key in enTranslations)) { + // deleted keys are fine + continue; + } + + const localParams = extractParams(enTranslations[key]); + const upstreamParams = extractParams(upstreamValue); + + assert.deepEqual( + localParams, + upstreamParams, + [ + `Translation key "${key}" changed placeholder parameters.`, + `This is a breaking change for existing translations.`, + `Please rename the translation key instead of changing placeholders.`, + ``, + `your version: ${[...localParams].join(", ")}`, + `on master: ${[...upstreamParams].join(", ")}`, + ].join("\n") + ); + } + }); }); From cbb8ad449915faee90a7e1613d5ba5f591f18161 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Mon, 12 Jan 2026 12:21:17 +0100 Subject: [PATCH 2/4] chore: rework the new contributor workflow (#6683) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/new_contributor_pr.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/new_contributor_pr.yml b/.github/workflows/new_contributor_pr.yml index 100ff18b3..38c0a8f46 100644 --- a/.github/workflows/new_contributor_pr.yml +++ b/.github/workflows/new_contributor_pr.yml @@ -4,9 +4,9 @@ on: # Safety # This workflow uses pull_request_target so it can run with write permissions on first-time contributor PRs. # It is safe because it does not check out or execute any code from the pull request and - # only uses the pinned, trusted actions/first-interaction action + # only uses the pinned, trusted plbstl/first-contribution action pull_request_target: # zizmor: ignore[dangerous-triggers] - types: [opened] + types: [opened, closed] branches: - master @@ -20,20 +20,21 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/first-interaction@1c4688942c71f71d4f5502a26ea67c331730fa4d # v3.1.0 + - uses: plbstl/first-contribution@4b2b042fffa26792504a18e49aa9543a87bec077 # v4.1.0 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - issue_message: "This is a required parameter, but we don't run on this event as we consider it a bit spammy.." - pr_message: | - Hello! Thank you for your contribution ๐Ÿ‘‹ + pr-reactions: rocket + pr-opened-msg: > + Hello and thanks for lending a paw to Uptime Kuma! ๐Ÿป๐Ÿ‘‹ - As this is your first contribution, please be sure to check out our - [Pull Request guidelines](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma). + As this is your first contribution, please be sure to check out our [Pull Request guidelines](https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md#can-i-create-a-pull-request-for-uptime-kuma). In particular: - Mark your PR as Draft while youโ€™re still making changes - - Mark it as Ready for review once itโ€™s fully ready ๐ŸŸข + - Mark it as Ready for review once itโ€™s fully ready If you have any design or process questions, feel free to ask them right here in this pull request - unclear documentation is a bug too. + pr-merged-msg: > + @{fc-author} congrats on your first contribution to Uptime Kuma! ๐Ÿป - Thanks for lending a paw to Uptime Kuma ๐Ÿป + We hope you enjoy contributing to our project and look forward to seeing more of your work in the future! + If you want to see your contribution in action, please see our [nightly builds here](https://hub.docker.com/layers/louislam/uptime-kuma/nightly2). From 4c99f92cd3cccb1723d1127249d5b9aaa81f7f0d Mon Sep 17 00:00:00 2001 From: Hemanth Rachapalli <92920794+hemanth5544@users.noreply.github.com> Date: Mon, 12 Jan 2026 16:59:26 +0530 Subject: [PATCH 3/4] feat: Rework how selected actions to perform pause,resume work and add a bulk-delete option (#6676) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Frank Elsinga --- src/components/MonitorList.vue | 348 +++++++++++++++++++++++---- src/components/MonitorListFilter.vue | 15 +- src/lang/en.json | 13 +- 3 files changed, 310 insertions(+), 66 deletions(-) diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index c77eed02c..032345487 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -2,20 +2,29 @@
- +
+ + +
-
-
- - - +
+ +
+ +
@@ -30,25 +39,51 @@
-
- -
- -
- - - - - - - {{ $t("selectedMonitorCount", [selectedMonitorCount]) }} +
+ +
+ + {{ $tc("selectedMonitorCountMsg", selectedMonitorCount) }}
@@ -81,6 +116,10 @@ {{ $t("pauseMonitorMsg") }} + + + {{ $t("deleteMonitorsMsg") }} +