From 0630f19255cadd0df938d707eff6ab66fd70f286 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Mon, 23 Feb 2026 17:05:59 +0100 Subject: [PATCH 1/2] IONOS(ionos-mail): ci: add localization JSON linting workflow This commit introduces a new GitHub Actions workflow for linting localization JSON files. The workflow checks for valid JSON syntax in the l10n directory during pull requests, ensuring that all localization files are correctly formatted before merging. The linting process runs on a dedicated job and provides a summary status to indicate any issues found. Signed-off-by: Misha M.-Kupriyanov --- .eslintrc.js | 15 +++ .github/workflows/lint-l10n.yml | 88 +++++++++++++++++ package-lock.json | 168 +++++++++++++++++++++++++++++++- package.json | 3 + 4 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/lint-l10n.yml diff --git a/.eslintrc.js b/.eslintrc.js index 55eb1f4619..e4a718ba89 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,6 +9,21 @@ module.exports = { plugins: [ 'perfectionist' ], + overrides: [ + { + files: ['l10n/*.json'], + parser: 'jsonc-eslint-parser', + plugins: ['jsonc'], + rules: { + // Catch trailing commas (the class of bug that broke translations in commit 48cc7ad) + 'jsonc/comma-dangle': ['error', 'never'], + // Disable JS-style rules inherited from @nextcloud that produce noise on auto-generated files + 'comma-spacing': 'off', + 'eol-last': 'off', + 'no-irregular-whitespace': 'off', + }, + }, + ], globals: { expect: true, OC: true, diff --git a/.github/workflows/lint-l10n.yml b/.github/workflows/lint-l10n.yml new file mode 100644 index 0000000000..fa8e88530f --- /dev/null +++ b/.github/workflows/lint-l10n.yml @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: 2026 STRATO GmbH +# SPDX-License-Identifier: AGPL-3.0-or-later + +name: Lint l10n + +on: pull_request + +permissions: + contents: read + +concurrency: + group: lint-l10n-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + changes: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + + outputs: + src: ${{ steps.changes.outputs.src }} + + steps: + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: changes + continue-on-error: true + with: + filters: | + src: + - '.github/workflows/**' + - 'l10n/**' + - '.eslintrc.*' + - 'package.json' + - 'package-lock.json' + + lint: + runs-on: ubuntu-latest + + needs: changes + if: needs.changes.outputs.src != 'false' + + name: l10n JSON lint + + steps: + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: Read package.json node and npm engines version + uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 + id: versions + with: + fallbackNode: '^20' + fallbackNpm: '^10' + + - name: Set up node ${{ steps.versions.outputs.nodeVersion }} + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: ${{ steps.versions.outputs.nodeVersion }} + + - name: Set up npm ${{ steps.versions.outputs.npmVersion }} + run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' + + - name: Install dependencies + env: + CYPRESS_INSTALL_BINARY: 0 + PUPPETEER_SKIP_DOWNLOAD: true + run: npm ci + + - name: Lint l10n JSON files + run: npm run lint:l10n + + summary: + permissions: + contents: none + runs-on: ubuntu-latest + needs: [changes, lint] + + if: always() + + name: l10n + + steps: + - name: Summary status + run: if ${{ needs.changes.outputs.src != 'false' && needs.lint.result != 'success' }}; then exit 1; fi diff --git a/package-lock.json b/package-lock.json index f0487abfe7..11b28c22b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "nextcloud-mail", - "version": "5.5.9", + "version": "5.5.11", "license": "AGPL-3.0-only", "dependencies": { "@ckeditor/ckeditor5-alignment": "44.3.0", @@ -113,6 +113,7 @@ "css-loader": "^7.1.2", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsonc": "^2.21.1", "eslint-plugin-perfectionist": "^4.15.0", "eslint-plugin-vue": "^9.33.0", "file-loader": "^6.2.0", @@ -6672,6 +6673,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@playwright/test": { "version": "1.54.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.2.tgz", @@ -11532,6 +11546,28 @@ } } }, + "node_modules/eslint-json-compat-utils": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/eslint-json-compat-utils/-/eslint-json-compat-utils-0.2.2.tgz", + "integrity": "sha512-KcTUifi8VSSHkrOY0FzB7smuTZRU9T2nCrcCy6k2b+Q77+uylBQVIxN4baVCIWvWJEpud+IsrYgco4JJ6io05g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esquery": "^1.6.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": "*", + "jsonc-eslint-parser": "^2.4.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "@eslint/json": { + "optional": true + } + } + }, "node_modules/eslint-module-utils": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", @@ -11712,6 +11748,72 @@ "node": ">=10" } }, + "node_modules/eslint-plugin-jsonc": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.21.1.tgz", + "integrity": "sha512-dbNR5iEnQeORwsK2WZzr3QaMtFCY3kKJVMRHPzUpKzMhmVy2zIpVgFDpX8MNoIdoqz6KCpCfOJavhfiSbZbN+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.5.1", + "diff-sequences": "^27.5.1", + "eslint-compat-utils": "^0.6.4", + "eslint-json-compat-utils": "^0.2.1", + "espree": "^9.6.1 || ^10.3.0", + "graphemer": "^1.4.0", + "jsonc-eslint-parser": "^2.4.0", + "natural-compare": "^1.4.0", + "synckit": "^0.6.2 || ^0.7.3 || ^0.11.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-jsonc/node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/eslint-plugin-jsonc/node_modules/eslint-compat-utils": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.6.5.tgz", + "integrity": "sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-jsonc/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-plugin-n": { "version": "16.6.2", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", @@ -13198,8 +13300,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "peer": true + "dev": true }, "node_modules/handlebars": { "version": "4.7.8", @@ -15663,6 +15764,51 @@ "node": ">=6" } }, + "node_modules/jsonc-eslint-parser": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.2.tgz", + "integrity": "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.5.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, + "node_modules/jsonc-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/jsonc-eslint-parser/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -21054,6 +21200,22 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", diff --git a/package.json b/package.json index e32db69558..93e19df22e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "watch": "NODE_ENV=development webpack --progress --watch --config webpack.dev.js", "lint": "eslint --ext .js,.ts,.vue --ignore-pattern tests src", "lint:fix": "eslint --ext .js,.ts,.vue --ignore-pattern tests src --fix", + "lint:l10n": "eslint l10n/*.json", + "lint:l10n:fix": "eslint --fix l10n/*.json", "stylelint": "stylelint \"css/*.css\" \"css/*.scss\" \"src/**/*.scss\" \"src/**/*.vue\"", "stylelint:fix": "stylelint \"css/*.css\" \"css/*.scss\" \"src/**/*.scss\" \"src/**/*.vue\" --fix", "test:unit": "jest src/tests/unit", @@ -130,6 +132,7 @@ "css-loader": "^7.1.2", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsonc": "^2.21.1", "eslint-plugin-perfectionist": "^4.15.0", "eslint-plugin-vue": "^9.33.0", "file-loader": "^6.2.0", From 48171f868beba220af18c7f670662b9ce62e20f3 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Mon, 23 Feb 2026 17:56:11 +0100 Subject: [PATCH 2/2] IONOS(ionos-mail): fix(i18n): correct translation strings for password save message Updated translation strings across multiple language files to ensure consistency in the message prompting users to save their passwords for security reasons. This change enhances the user experience by providing clear and accurate instructions in various languages. Should be done in 48cc7ad Signed-off-by: Misha M.-Kupriyanov --- l10n/de_DE.json | 2 +- l10n/en_GB.json | 2 +- l10n/es.json | 4 ++-- l10n/fr.json | 2 +- l10n/it.json | 2 +- l10n/nl.json | 2 +- l10n/sv.json | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/l10n/de_DE.json b/l10n/de_DE.json index ca84c065a4..0eabb721fa 100644 --- a/l10n/de_DE.json +++ b/l10n/de_DE.json @@ -886,6 +886,6 @@ "IMAP access / password" : "IMAP-Zugang/Passwort", "Generate password" : "Passwort generieren", "Copy password" : "Passwort kopieren", - "Please save this password now. For security reasons, it will not be shown again." : "Bitte speichern Sie dieses Passwort jetzt. Aus Sicherheitsgründen wird es nicht erneut angezeigt.", + "Please save this password now. For security reasons, it will not be shown again." : "Bitte speichern Sie dieses Passwort jetzt. Aus Sicherheitsgründen wird es nicht erneut angezeigt." },"pluralForm" :"nplurals=2; plural=(n != 1);" } diff --git a/l10n/en_GB.json b/l10n/en_GB.json index db9f7302af..6ae8e3f167 100644 --- a/l10n/en_GB.json +++ b/l10n/en_GB.json @@ -872,7 +872,7 @@ "Mailvelope is a browser extension that enables easy OpenPGP encryption and decryption for emails." : "Mailvelope is a browser extension that enables easy OpenPGP encryption and decryption for emails.", "Step 1: Install Mailvelope browser extension" : "Step 1: Install Mailvelope browser extension", "Step 2: Enable Mailvelope for the current domain" : "Step 2: Enable Mailvelope for the current domain", - "To access your mailbox via IMAP, you can generate an app-specific password. This password allows IMAP clients to connect to your account." : "To access your mailbox via IMAP, you can generate an app-specific password. This password allows IMAP clients to connect to your account." + "To access your mailbox via IMAP, you can generate an app-specific password. This password allows IMAP clients to connect to your account." : "To access your mailbox via IMAP, you can generate an app-specific password. This password allows IMAP clients to connect to your account.", "IMAP access / password" : "IMAP access / password", "Generate password" : "Generate password", "Copy password" : "Copy password", diff --git a/l10n/es.json b/l10n/es.json index 30d6e0403c..95717b8aa0 100644 --- a/l10n/es.json +++ b/l10n/es.json @@ -884,6 +884,6 @@ "IMAP access / password" : "Acceso IMAP / contraseña", "Generate password" : "Generar contraseña", "Copy password" : "Copiar contraseña", - "Please save this password now. For security reasons, it will not be shown again." : "Guarde esta contraseña ahora. Por motivos de seguridad, no se volverá a mostrar.", -,"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;" + "Please save this password now. For security reasons, it will not be shown again." : "Guarde esta contraseña ahora. Por motivos de seguridad, no se volverá a mostrar." +},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;" } diff --git a/l10n/fr.json b/l10n/fr.json index 203424ea73..8b3502ed6d 100644 --- a/l10n/fr.json +++ b/l10n/fr.json @@ -888,6 +888,6 @@ "IMAP access / password" : "Accès IMAP / mot de passe", "Generate password" : "générer un mot de passe", "Copy password" : "copier le mot de passe", - "Please save this password now. For security reasons, it will not be shown again." : "Veuillez enregistrer ce mot de passe dès maintenant. Pour des raisons de sécurité, il ne sera plus affiché.", + "Please save this password now. For security reasons, it will not be shown again." : "Veuillez enregistrer ce mot de passe dès maintenant. Pour des raisons de sécurité, il ne sera plus affiché." },"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;" } diff --git a/l10n/it.json b/l10n/it.json index 6cb5a7456c..d72756e24d 100644 --- a/l10n/it.json +++ b/l10n/it.json @@ -577,6 +577,6 @@ "IMAP access / password" : "Accesso IMAP / password", "Generate password" : "generare password", "Copy password" : "copia password", - "Please save this password now. For security reasons, it will not be shown again." : "Salva questa password adesso. Per motivi di sicurezza, non verrà più visualizzata.", + "Please save this password now. For security reasons, it will not be shown again." : "Salva questa password adesso. Per motivi di sicurezza, non verrà più visualizzata." },"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;" } diff --git a/l10n/nl.json b/l10n/nl.json index 257a94d030..02265982c3 100644 --- a/l10n/nl.json +++ b/l10n/nl.json @@ -464,6 +464,6 @@ "IMAP access / password" : "IMAP-toegang / wachtwoord", "Generate password" : "wachtwoord genereren", "Copy password" : "wachtwoord kopiëren", - "Please save this password now. For security reasons, it will not be shown again." : "Sla dit wachtwoord nu op. Om veiligheidsredenen wordt het niet opnieuw weergegeven.", + "Please save this password now. For security reasons, it will not be shown again." : "Sla dit wachtwoord nu op. Om veiligheidsredenen wordt het niet opnieuw weergegeven." },"pluralForm" :"nplurals=2; plural=(n != 1);" } diff --git a/l10n/sv.json b/l10n/sv.json index 481aef7735..6fced3815d 100644 --- a/l10n/sv.json +++ b/l10n/sv.json @@ -394,6 +394,6 @@ "IMAP access / password" : "IMAP-åtkomst / lösenord", "Generate password" : "generera lösenord", "Copy password" : "kopiera lösenord", - "Please save this password now. For security reasons, it will not be shown again." : "Spara detta lösenord nu. Av säkerhetsskäl kommer det inte att visas igen.", + "Please save this password now. For security reasons, it will not be shown again." : "Spara detta lösenord nu. Av säkerhetsskäl kommer det inte att visas igen." },"pluralForm" :"nplurals=2; plural=(n != 1);" }