From e0f9d3c45961af9353be689cedc335a02630a7e7 Mon Sep 17 00:00:00 2001 From: PierreDemailly Date: Sun, 18 Feb 2024 16:18:45 +0100 Subject: [PATCH] feat: improve i18n --- .github/workflows/codeql.yml | 8 +- .github/workflows/nodejs.yml | 6 +- .github/workflows/scorecards.yml | 6 +- .github/workflows/size-satisfies.yml | 6 +- .github/workflows/vis-network.yml | 4 +- LICENSE | 2 +- bin/index.js | 12 +- i18n/english.js | 141 +++++++++++++++++- i18n/french.js | 141 +++++++++++++++++- package.json | 31 ++-- public/common/utils.js | 8 +- public/components/expandable/expandable.js | 22 +-- public/components/locker/locker.css | 1 + public/components/locker/locker.js | 8 +- public/components/package/header/header.js | 3 +- .../package/pannels/overview/overview.js | 50 +++++-- .../package/pannels/warnings/code-fetcher.js | 1 - public/components/searchbar/searchbar.js | 6 +- public/components/views/home/home.js | 22 ++- public/core/i18n.js | 8 + public/main.js | 11 +- src/http-server/endpoints/i18n.js | 10 ++ src/http-server/index.js | 2 + test/httpServer.test.js | 8 + views/index.html | 2 +- workspaces/vis-network/src/constants.js | 27 ++-- workspaces/vis-network/src/network.js | 14 +- workspaces/vis-network/src/utils.js | 7 + 28 files changed, 466 insertions(+), 101 deletions(-) create mode 100644 public/core/i18n.js create mode 100644 src/http-server/endpoints/i18n.js diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bd7e3ff1..6caadc0f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -41,7 +41,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs @@ -50,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 + uses: github/codeql-action/init@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -63,7 +63,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 + uses: github/codeql-action/autobuild@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -76,6 +76,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 + uses: github/codeql-action/analyze@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3 with: category: "/language:${{matrix.language}}" \ No newline at end of file diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 07b4634d..b615b33a 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -18,13 +18,13 @@ jobs: fail-fast: false steps: - name: Harden Runner - uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: ${{ matrix.node-version }} - name: Install dependencies @@ -34,4 +34,4 @@ jobs: - name: Run tests run: npm run coverage - name: Send coverage report to Codecov - uses: codecov/codecov-action@428cda1b1c731be3e8bfa389049c3f276d572ffb # v4.0.0-beta.3 + uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index e51d326a..df26c188 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs @@ -64,7 +64,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: SARIF file path: results.sarif @@ -72,6 +72,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 + uses: github/codeql-action/upload-sarif@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3 with: sarif_file: results.sarif diff --git a/.github/workflows/size-satisfies.yml b/.github/workflows/size-satisfies.yml index 995eee66..fd83be7c 100644 --- a/.github/workflows/size-satisfies.yml +++ b/.github/workflows/size-satisfies.yml @@ -21,13 +21,13 @@ jobs: fail-fast: false steps: - name: Harden Runner - uses: step-security/harden-runner@128a63446a954579617e875aaab7d2978154e969 # v2.4.0 + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: ${{ matrix.node-version }} - name: Install dependencies diff --git a/.github/workflows/vis-network.yml b/.github/workflows/vis-network.yml index 84dc384c..91726566 100644 --- a/.github/workflows/vis-network.yml +++ b/.github/workflows/vis-network.yml @@ -22,13 +22,13 @@ jobs: fail-fast: false steps: - name: Harden Runner - uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: ${{ matrix.node-version }} - name: Install dependencies diff --git a/LICENSE b/LICENSE index a9a1dfdf..0e5b2476 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 NodeSecure +Copyright (c) 2022-2024 NodeSecure Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/bin/index.js b/bin/index.js index 8293f815..35d62837 100755 --- a/bin/index.js +++ b/bin/index.js @@ -91,7 +91,7 @@ prog prog .command("scorecard [repository]") .describe(i18n.getTokenSync("cli.commands.scorecard.desc")) - .option("--vcs", "Version control platform (GitHub, GitLab", "github") + .option("--vcs", i18n.getTokenSync("cli.commands.scorecard.option_vcs"), "github") .action(commands.scorecard.main); prog @@ -101,13 +101,13 @@ prog prog .command("config create [configuration]") - .option("-c, --cwd", "create config file at the cwd", false) - .describe("Init your Nodesecure config file") + .option("-c, --cwd", i18n.getTokenSync("cli.commands.configCreate.option_cwd"), false) + .describe(i18n.getTokenSync("cli.commands.configCreate.desc")) .action(commands.config.createConfigFile); prog .command("config") - .describe("Edit your nodesecure config file") + .describe(i18n.getTokenSync("cli.commands.config.desc")) .action(commands.config.editConfigFile); prog.parse(process.argv); @@ -117,7 +117,7 @@ function defaultScannerCommand(name, options = {}) { const cmd = prog.command(name) .option("-d, --depth", i18n.getTokenSync("cli.commands.option_depth"), 4) - .option("--silent", "enable silent mode which disable CLI spinners", false); + .option("--silent", i18n.getTokenSync("cli.commands.option_silent"), false); if (includeOutput) { cmd.option("-o, --output", i18n.getTokenSync("cli.commands.option_output"), "nsecure-result"); @@ -134,7 +134,7 @@ function checkNodeSecureToken() { const varEnvName = kleur.yellow().bold("NODE_SECURE_TOKEN"); console.log( - kleur.red().bold(`Environment variable ${varEnvName} is missing!\n`) + kleur.red().bold(`${i18n.getTokenSync("cli.missingEnv", varEnvName)}\n`) ); } } diff --git a/i18n/english.js b/i18n/english.js index 9380a79f..26a4161f 100644 --- a/i18n/english.js +++ b/i18n/english.js @@ -1,7 +1,67 @@ /* eslint-disable max-len */ +// Import Third-party Dependencies import { taggedString as tS } from "@nodesecure/i18n"; const cli = { + executing_at: "Executing node-secure at", + min_nodejs_version: tS`node-secure requires at least Node.js ${0} to work! Please upgrade your Node.js version.`, + no_dep_to_proceed: "No dependencies to proceed!", + successfully_written_json: tS`Successfully written results file at: ${0}`, + http_server_started: "HTTP Server started on:", + missingEnv: tS`Environment variable ${0} is missing!`, + commands: { + option_depth: "Maximum dependencies depth to fetch", + option_output: "Json file output name", + option_silent: "enable silent mode which disable CLI spinners", + strategy: "Vulnerabilities source to use", + hydrate_db: { + desc: "Hydrate the vulnerabilities db", + running: tS`Hydrating local vulnerabilities with the '${0}' database...`, + success: tS`Successfully hydrated vulnerabilities database in ${0}` + }, + cwd: { + desc: "Run security analysis on the current working dir", + option_nolock: "Disable usage of package-lock.json", + option_full: "Enable full analysis of packages in the package-lock.json file" + }, + from: { + desc: "Run security analysis on a given package from npm registry", + searching: tS`Searching for '${0}' manifest in the npm registry...`, + fetched: tS`Fetched ${0} manifest from npm in ${1}` + }, + auto: { + desc: "Run security analysis on cwd or a given package and automatically open the web interface", + option_keep: "Keep the nsecure-result.json file on the system after execution" + }, + open: { + desc: "Run an HTTP Server with a given nsecure JSON file", + option_port: "Define the running port" + }, + verify: { + desc: "Run a complete advanced analysis for a given npm package", + option_json: "Stdout the analysis payload" + }, + summary: { + desc: "Display your analysis results", + warnings: "Warnings" + }, + lang: { + desc: "Configure the CLI default language", + question_text: "What language do you want to use?", + new_selection: tS`'${0}' has been selected as the new CLI language!` + }, + scorecard: { + desc: "Display the OSSF Scorecard for a given repository or the current working directory (Github only, e.g. fastify/fastify)", + option_vcs: "Version control platform (GitHub, GitLab)" + }, + config: { + desc: "Edit your NodeSecure config file" + }, + configCreate: { + desc: "Init your Nodesecure config file", + option_cwd: "Create config file at the cwd" + } + }, startHttp: { invalidScannerVersion: tS`the payload has been scanned with version '${0}' and do not satisfies the required CLI range '${1}'`, regenerate: "please re-generate a new JSON payload using the CLI" @@ -9,6 +69,63 @@ const cli = { }; const ui = { + stats: { + title: "Global Stats", + total_packages: "Total of packages", + total_size: "Total size", + indirect_deps: "Packages with indirect dependencies", + extensions: "Extensions", + licenses: "Licenses", + maintainers: "Maintainers" + }, + package_info: { + navigation: { + overview: "overview", + files: "files", + dependencies: "scripts & dependencies", + warnings: "threats in source code", + vulnerabilities: "vulnerabilities (CVE)", + licenses: "licenses conformance (SPDX)" + }, + title: { + maintainers: "maintainers", + releases: "releases", + files: "files", + files_extensions: "files extensions", + unused_deps: "unused dependencies", + missing_deps: "missing dependencies", + minified_files: "minified files", + node_deps: "node.js dependencies", + third_party_deps: "third-party dependencies", + required_files: "required files", + used_by: "used by", + openSsfScorecard: "Security Scorecard" + }, + overview: { + homepage: "Homepage", + author: "Author", + size: "Size on system", + dependencies: "Number of dependencies", + files: "Number of files", + tsTypings: "TS Typings", + node: "Node.js Compatibility", + npm: "NPM Compatibility", + lastReleaseVersion: "Last release version", + lastReleaseDate: "Last release date", + publishedReleases: "Number of published releases", + numberPublishers: "Number of publisher(s)" + }, + helpers: { + warnings: "Learn more about warnings in the", + spdx: "Learn more about the SPDX project", + here: "here", + openSsf: "Learn more about the OpenSSF Scorecards", + thirdPartyTools: "Third-party tools" + } + }, + searchbar_placeholder: "Search", + loading_nodes: "... Loading nodes ...", + please_wait: "(Please wait)", popup: { maintainer: { intree: "packages in the dependency tree" @@ -16,11 +133,18 @@ const ui = { }, home: { overview: { - title: "Overview" + title: "Overview", + dependencies: "dependencies", + totalSize: "total size", + directDeps: "direct deps", + transitiveDeps: "transitive deps", + downloadsLastWeek: "downloads last week" }, watch: "Packages in the dependency tree requiring greater attention", criticalWarnings: "Critical Warnings", - maintainers: "Maintainers" + maintainers: "Maintainers", + showMore: "show more", + showLess: "show less" }, settings: { general: { @@ -37,6 +161,19 @@ const ui = { openCloseWiki: "Open/Close wiki", lock: "Lock/Unlock network" } + }, + network: { + childOf: "child of", + parentOf: "parent of", + unlocked: "unlocked", + locked: "locked" + }, + search: { + "File extensions": "File extensions", + "Node.js core modules": "Node.js core modules", + "Available licenses": "Available licenses", + "Available flags": "Available flags", + default: "Search options" } }; diff --git a/i18n/french.js b/i18n/french.js index d4a9c28a..28dda98c 100644 --- a/i18n/french.js +++ b/i18n/french.js @@ -1,7 +1,67 @@ /* eslint-disable max-len */ +// Import Third-party Dependencies import { taggedString as tS } from "@nodesecure/i18n"; const cli = { + executing_at: "Exécution de node-secure à", + min_nodejs_version: tS`node-secure nécessite au moins Node.js ${0} pour fonctionner ! Merci de mettre à jour votre version de Node.js.`, + no_dep_to_proceed: "Aucune dépendance pour continuer !", + successfully_written_json: tS`Ecriture du fichier de résultats réalisée avec succès ici : ${0}`, + http_server_started: "Serveur HTTP démarré sur :", + missingEnv: tS`La variable d'environnement ${0} est manquante!`, + commands: { + option_depth: "Niveau de profondeur de dépendances maximum à aller chercher", + option_output: "Nom de sortie du fichier json", + option_silent: "Activer le mode silencieux qui désactive les spinners du CLI", + strategy: "Source de vulnérabilités à utiliser", + hydrate_db: { + desc: "Mise à jour de la base de vulnérabilité", + running: tS`Mise à jour locale des vulnérabilités avec la base '${0}'...`, + success: tS`Base de vulnérabilités mise à jour avec succès en ${0}` + }, + cwd: { + desc: "Démarre une analyse de sécurité sur le dossier courant", + option_nolock: "Désactive l'utilisation du package-lock.json", + option_full: "Active l'analyse complète des packages présents dans le package-lock.json" + }, + from: { + desc: "Démarre une analyse de sécurité sur un package donné du registre npm", + searching: tS`Recherche du manifest '${0}' dans le registre npm...`, + fetched: tS`Manifest du package ${0} importé de npm en ${1}` + }, + auto: { + desc: "Démarre une analyse de sécurité sur le dossier courant ou sur un package donné et ouvre automatiquement l'interface web", + option_keep: "Conserve le fichier nsecure-result.json sur le systeme après l'exécution" + }, + open: { + desc: "Démarre un serveur HTTP avec un fichier .json nsecure donné", + option_port: "Port à utiliser" + }, + verify: { + desc: "Démarre une analyse AST avancée pour un package npm donné", + option_json: "Affiche le résultat d'analyse dans la sortie standard" + }, + summary: { + desc: "Afficher le résultat de votre analyse", + warnings: "Menaces" + }, + lang: { + desc: "Configure le langage par défaut du CLI", + question_text: "Quel langage souhaitez-vous utiliser ?", + new_selection: tS`'${0}' a été selectionné comme étant le nouveau langage du CLI !` + }, + scorecard: { + desc: "Afficher la fiche de score OSSF du repo donné ou du repertoire actuel (Github uniquement ex. fastify/fastify)", + option_vcs: "Logiciel de gestion de versions (GitHub, GitLab)" + }, + config: { + desc: "Modifier le fichier de configuration NodeSecure" + }, + configCreate: { + desc: "Initialiser le fichier de configuration Nodesecure", + option_cwd: "Créer le fichier dans le dossier courant" + } + }, startHttp: { invalidScannerVersion: tS`le fichier d'analyse correspond à la version '${0}' du scanner et ne satisfait pas la range '${1}' attendu par la CLI`, regenerate: "veuillez re-générer un nouveau fichier d'analyse JSON en utilisant votre CLI" @@ -9,6 +69,63 @@ const cli = { }; const ui = { + stats: { + title: "Stats Globales", + total_packages: "Total des packages", + total_size: "Poids total", + indirect_deps: "Packages avec dépendances indirectes", + extensions: "Extensions", + licenses: "Licences", + maintainers: "Mainteneurs" + }, + package_info: { + navigation: { + overview: "vue d'ensemble", + files: "fichiers", + dependencies: "scripts & dépendances", + warnings: "menaces dans le code", + vulnerabilities: "vulnérabilités", + licenses: "conformité des licences (SPDX)" + }, + title: { + maintainers: "mainteneurs", + releases: "versions publiées", + files: "fichiers", + files_extensions: "extensions des fichiers", + unused_deps: "dépendances non utilisées ", + missing_deps: "dépendances manquantes", + minified_files: "fichiers minifiés", + node_deps: "dépendances node.js", + third_party_deps: "dépendances tierces", + required_files: "fichiers requis", + used_by: "utilisé par", + openSsfScorecard: "Fiche de score de sécurité" + }, + overview: { + homepage: "Page d'accueil", + author: "Auteur", + size: "Poids sur le système", + dependencies: "Nombre de dépendances", + files: "Nombre de fichiers", + tsTypings: "Typages TS", + node: "Compatibilité Node.js", + npm: "Compatibilité NPM", + lastReleaseVersion: "Dernière version publiée", + lastReleaseDate: "Date de la dernière version", + publishedReleases: "Nombre de versions publiées", + numberPublishers: "Nombre de contributeur(s)" + }, + helpers: { + warnings: "En savoir plus sur les alertes avec le", + spdx: "En savoir plus sur le projet SPDX", + here: "ici", + openSsf: "En savoir plus sur les fiches de score OpenSSF", + thirdPartyTools: "Outils tiers" + } + }, + searchbar_placeholder: "Recherche", + loading_nodes: "... Chargement des noeuds ...", + please_wait: "(Merci de patienter)", popup: { maintainer: { intree: "packages dans l'abre de dépendances" @@ -16,11 +133,18 @@ const ui = { }, home: { overview: { - title: "Vue d'ensemble" + title: "Vue d'ensemble", + dependencies: "dépendances", + totalSize: "poids total", + directDeps: "dépendances directes", + transitiveDeps: "dépendances transitives", + downloadsLastWeek: "téléchargements la semaine dernière" }, watch: "Packages dans l'arbre de dépendance nécessitant une plus grande attention", criticalWarnings: "Avertissements critiques", - maintainers: "Mainteneurs" + maintainers: "Mainteneurs", + showMore: "voir plus", + showLess: "voir moins" }, settings: { general: { @@ -37,6 +161,19 @@ const ui = { openCloseWiki: "Ouverture/Fermeture du wiki", lock: "Verrouiller/Déverrouiller le réseau" } + }, + network: { + childOf: "enfant de", + parentOf: "parent de", + unlocked: "Déverrouillé", + locked: "Verrouillé" + }, + search: { + "File extensions": "Extensions de fichiers", + "Node.js core modules": "Modules de base de Node.js", + "Available licenses": "Licences disponibles", + "Available flags": "Drapeaux disponibles", + default: "Options de recherche" } }; diff --git a/package.json b/package.json index 307c8bb9..00404b72 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "prepublishOnly": "rimraf ./dist && npm run build && pkg-ok", "build": "node ./esbuild.config.js", "test": "npm run test-only && npm run eslint", - "test-only": "node --loader=esmock --no-warnings --test test/", + "test-only": "glob -c \"node --loader=esmock --no-warnings --test\" \"test/**/*.test.js\"", "coverage": "c8 --reporter=lcov npm run test" }, "files": [ @@ -61,16 +61,17 @@ }, "homepage": "https://github.com/NodeSecure/cli#readme", "devDependencies": { - "@myunisoft/httpie": "^2.0.1", - "@nodesecure/eslint-config": "^1.7.1", + "@myunisoft/httpie": "^3.0.3", + "@nodesecure/eslint-config": "^1.9.0", "@nodesecure/size-satisfies": "^1.1.0", "@nodesecure/vis-network": "^1.4.0", - "@types/node": "^20.5.3", - "c8": "^8.0.1", + "@types/node": "^20.11.19", + "c8": "^9.1.0", "cross-env": "^7.0.3", - "esbuild": "^0.19.2", - "eslint": "^8.47.0", - "esmock": "^2.3.8", + "esbuild": "^0.20.0", + "eslint": "^8.56.0", + "esmock": "^2.6.3", + "glob": "^10.3.10", "http-server": "^14.1.1", "pkg-ok": "^3.0.0", "pretty-bytes": "^6.1.1", @@ -82,7 +83,7 @@ "@nodesecure/flags": "^2.4.0", "@nodesecure/i18n": "^3.5.0", "@nodesecure/licenses-conformance": "^2.1.0", - "@nodesecure/npm-registry-sdk": "^2.0.0", + "@nodesecure/npm-registry-sdk": "^2.1.0", "@nodesecure/ossf-scorecard-sdk": "^3.1.0", "@nodesecure/rc": "^1.5.0", "@nodesecure/scanner": "^5.3.0", @@ -91,21 +92,21 @@ "@openally/result": "^1.2.0", "@polka/send-type": "^0.5.2", "@topcli/cliui": "^1.1.0", - "@topcli/prompts": "^1.7.0", + "@topcli/prompts": "^1.9.0", "@topcli/spinner": "^2.1.2", - "cacache": "^18.0.0", - "dotenv": "^16.3.1", + "cacache": "^18.0.2", + "dotenv": "^16.4.4", "filenamify": "^6.0.0", "highlightjs-line-numbers.js": "^2.8.0", "ini": "^4.1.1", "kleur": "^4.1.5", "ms": "^2.1.3", - "open": "^9.1.0", + "open": "^10.0.3", "polka": "^0.5.2", "sade": "^1.8.1", - "semver": "^7.5.4", + "semver": "^7.6.0", "server-destroy": "^1.0.1", - "sirv": "^2.0.3", + "sirv": "^2.0.4", "zup": "0.0.1" } } diff --git a/public/common/utils.js b/public/common/utils.js index 31e5c245..48e088f0 100644 --- a/public/common/utils.js +++ b/public/common/utils.js @@ -119,7 +119,7 @@ export function parseRepositoryUrl(repository = {}, defaultValue = null) { } } -function createImageElement(baseUrl, id = null){ +function createImageElement(baseUrl, id = null) { const imageElement = document.createElement("img"); if (id === null || id === "") { imageElement.src = `${avatarURL}`; @@ -265,3 +265,9 @@ export function hideOnClickOutside( return outsideClickListener; } + +export function currentLang() { + const detectedLang = document.getElementById("lang").dataset.lang; + + return detectedLang in window.i18n ? detectedLang : "english"; +} diff --git a/public/components/expandable/expandable.js b/public/components/expandable/expandable.js index 109119f5..9af6b1de 100644 --- a/public/components/expandable/expandable.js +++ b/public/components/expandable/expandable.js @@ -1,31 +1,17 @@ // Import Internal Dependencies -import { createDOMElement } from "../../common/utils"; - -// TODO: migrate this to ./i18n ? -const ki18nTranslaction = { - french: { - showMore: "voir plus", - showLess: "voir moins" - }, - english: { - showMore: "show more", - showLess: "show less" - } -}; +import { createDOMElement, currentLang } from "../../common/utils"; export function createExpandableSpan( hideItemsLength, onclick = () => void 0 ) { - const detectedLang = document.getElementById("lang").dataset.lang; - const lang = detectedLang in ki18nTranslaction ? detectedLang : "english"; - + const lang = currentLang(); const span = createDOMElement("span", { classList: ["expandable"], attributes: { "data-value": "closed" }, childs: [ createDOMElement("i", { className: "icon-plus-squared-alt" }), - createDOMElement("p", { text: ki18nTranslaction[lang].showMore }) + createDOMElement("p", { text: window.i18n[lang].home.showMore }) ] }); span.addEventListener("click", function itemListClickAction() { @@ -37,7 +23,7 @@ export function createExpandableSpan( } this.querySelector("p").textContent = isClosed ? - ki18nTranslaction[lang].showLess : ki18nTranslaction[lang].showMore; + window.i18n[lang].home.showLess : window.i18n[lang].home.showMore; this.setAttribute("data-value", isClosed ? "opened" : "closed"); for (let id = 0; id < this.parentNode.childNodes.length; id++) { diff --git a/public/components/locker/locker.css b/public/components/locker/locker.css index c6c7059b..1b981272 100644 --- a/public/components/locker/locker.css +++ b/public/components/locker/locker.css @@ -46,4 +46,5 @@ display: flex; align-items: center; height: inherit; + text-transform: capitalize; } diff --git a/public/components/locker/locker.js b/public/components/locker/locker.js index 144c8e2a..22192213 100644 --- a/public/components/locker/locker.js +++ b/public/components/locker/locker.js @@ -1,3 +1,5 @@ +// Import Internal Dependencies +import * as utils from "../../common/utils.js"; export class Locker { constructor(nsn) { @@ -68,13 +70,13 @@ export class Locker { } else if (this.nsn.lastHighlightedIds !== null) { this.nsn.lastHighlightedIds = null; - this.nsn.neighbourHighlight(selectedNode); + this.nsn.neighbourHighlight(selectedNode, window.i18n[utils.currentLang()]); } } renderLock() { this.dom.classList.add("enabled"); - this.dom.querySelector("p").textContent = "LOCKED"; + this.dom.querySelector("p").textContent = window.i18n[utils.currentLang()].network.locked; this.networkView.classList.add("locked"); const iconElement = this.dom.querySelector("i"); @@ -85,7 +87,7 @@ export class Locker { renderUnlock() { this.dom.classList.remove("enabled"); - this.dom.querySelector("p").textContent = "UNLOCKED"; + this.dom.querySelector("p").textContent = window.i18n[utils.currentLang()].network.unlocked; this.networkView.classList.remove("locked"); const iconElement = this.dom.querySelector("i"); diff --git a/public/components/package/header/header.js b/public/components/package/header/header.js index b1c220a5..367d3ce0 100644 --- a/public/components/package/header/header.js +++ b/public/components/package/header/header.js @@ -156,12 +156,13 @@ export class PackageHeader { */ renderMenu(packageName) { const { snykAdvisor, socket } = PackageHeader.ExternalLinks; + const i18n = window.i18n[utils.currentLang()]; return utils.createDOMElement("div", { classList: ["info-menu", "hidden"], childs: [ utils.createDOMElement("div", { - text: "Third-party tools", + text: i18n.package_info.helpers.thirdPartyTools, classList: ["info-menu-title"] }), utils.createDOMElement("a", { diff --git a/public/components/package/pannels/overview/overview.js b/public/components/package/pannels/overview/overview.js index 73bfff65..29708cdb 100644 --- a/public/components/package/pannels/overview/overview.js +++ b/public/components/package/pannels/overview/overview.js @@ -117,24 +117,41 @@ export class Overview { const { metadata } = this.package.dependency; const fragment = document.createDocumentFragment(); + const i18n = window.i18n[utils.currentLang()]; const { homepage } = this.package.links; if (typeof homepage.href === "string") { - fragment.appendChild(utils.createLiField("Homepage", homepage.href, { isLink: true })); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.homepage, homepage.href, { isLink: true } + )); } - fragment.appendChild(utils.createLiField("Author", this.author)); - fragment.appendChild(utils.createLiField("Size on system", prettyBytes(size))); - fragment.appendChild(utils.createLiField("Number of dependencies", metadata.dependencyCount)); - fragment.appendChild(utils.createLiField("Number of files", composition.files.length)); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.author, this.author + )); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.size, prettyBytes(size) + )); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.dependencies, metadata.dependencyCount + )); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.files, composition.files.length + )); fragment.appendChild( utils.createLiField("README.md", composition.files.some((file) => /README\.md/gi.test(file)) ? "✔️" : "❌") ); - fragment.appendChild(utils.createLiField("TS Typings", composition.files.some((file) => /d\.ts/gi.test(file)) ? "✔️" : "❌")); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.tsTypings, composition.files.some((file) => /d\.ts/gi.test(file)) ? "✔️" : "❌" + )); if ("node" in engines) { - fragment.appendChild(utils.createLiField("Node.js compatibility", engines.node)); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.npm, engines.node + )); } if ("npm" in engines) { - fragment.appendChild(utils.createLiField("NPM compatibility", engines.npm)); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.npm, engines.npm + )); } return fragment; @@ -143,15 +160,24 @@ export class Overview { renderReleases() { const { metadata } = this.package.dependency; const fragment = document.createDocumentFragment(); + const i18n = window.i18n[utils.currentLang()]; const lastUpdatedAt = kEnGBDateFormat.format( new Date(this.package.dependency.metadata.lastUpdateAt) ); - fragment.appendChild(utils.createLiField("Last release version", metadata.lastVersion)); - fragment.appendChild(utils.createLiField("Last release date", lastUpdatedAt)); - fragment.appendChild(utils.createLiField("Number of published releases", metadata.publishedCount)); - fragment.appendChild(utils.createLiField("Number of publisher(s)", metadata.publishers.length)); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.lastReleaseVersion, metadata.lastVersion + )); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.lastReleaseDate, lastUpdatedAt + )); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.publishedReleases, metadata.publishedCount + )); + fragment.appendChild(utils.createLiField( + i18n.package_info.overview.numberPublishers, metadata.publishers.length + )); return fragment; } diff --git a/public/components/package/pannels/warnings/code-fetcher.js b/public/components/package/pannels/warnings/code-fetcher.js index bcfda16c..eb207837 100644 --- a/public/components/package/pannels/warnings/code-fetcher.js +++ b/public/components/package/pannels/warnings/code-fetcher.js @@ -101,7 +101,6 @@ export class CodeFetcher { const tdElement = tdsElement[i]; if (lineIndex <= i && endLine >= startFrom + i) { - console.log("ta mere nan ?"); tdElement.classList.add("relevant-line"); } } diff --git a/public/components/searchbar/searchbar.js b/public/components/searchbar/searchbar.js index 6e7e15eb..0fa8ae9a 100644 --- a/public/components/searchbar/searchbar.js +++ b/public/components/searchbar/searchbar.js @@ -3,7 +3,7 @@ import semver from "semver"; import sizeSatisfies from "@nodesecure/size-satisfies"; // Import Internal Dependencies -import { createDOMElement, vec2Distance } from "../../common/utils.js"; +import { createDOMElement, vec2Distance, currentLang } from "../../common/utils.js"; // CONSTANTS const kFiltersName = new Set(["package", "version", "flag", "license", "author", "ext", "builtin", "size"]); @@ -283,7 +283,9 @@ export class SearchBar { }); }); - const titleText = Reflect.has(kHelpersTitleName, filterName) ? kHelpersTitleName[filterName] : "Options de recherche"; + const titleText = window.i18n[currentLang()].search[ + Reflect.has(kHelpersTitleName, filterName) ? kHelpersTitleName[filterName] : "default" + ]; // eslint-disable-next-line max-len this.helper.innerHTML = `

${titleText}

`; this.helper.appendChild(clone); diff --git a/public/components/views/home/home.js b/public/components/views/home/home.js index 2df2ed04..f8e0cf7a 100644 --- a/public/components/views/home/home.js +++ b/public/components/views/home/home.js @@ -36,6 +36,7 @@ export class HomeView { ) { this.secureDataSet = secureDataSet; this.nsn = nsn; + this.lang = utils.currentLang(); this.generateScorecard(); this.generateHeader(); @@ -139,11 +140,18 @@ export class HomeView { directDependencies++; } } - - fragment.appendChild(createOverviewDiv("icon-cubes", "# dependencies", this.secureDataSet.dependenciesCount)); - fragment.appendChild(createOverviewDiv("icon-archive", "total size", this.secureDataSet.prettySize)); - fragment.appendChild(createOverviewDiv("icon-link", "# direct deps", directDependencies)); - fragment.appendChild(createOverviewDiv("icon-sitemap", "# transitive deps", this.secureDataSet.indirectDependencies)); + fragment.appendChild(createOverviewDiv( + "icon-cubes", `# ${window.i18n[this.lang].home.overview.dependencies}`, this.secureDataSet.dependenciesCount + )); + fragment.appendChild(createOverviewDiv( + "icon-archive", window.i18n[this.lang].home.overview.totalSize, this.secureDataSet.prettySize + )); + fragment.appendChild(createOverviewDiv( + "icon-link", `# ${window.i18n[this.lang].home.overview.directDeps}`, directDependencies + )); + fragment.appendChild(createOverviewDiv( + "icon-sitemap", `# ${window.i18n[this.lang].home.overview.transitiveDeps}`, this.secureDataSet.indirectDependencies + )); const homeOverviewElement = document.querySelector(".home--overview"); homeOverviewElement.appendChild(fragment); @@ -153,7 +161,9 @@ export class HomeView { if (downloads) { const formattedNumber = new Intl.NumberFormat("de-DE").format(downloads); - homeOverviewElement.appendChild(createOverviewDiv("icon-chart-bar", "downloads last week", formattedNumber)); + homeOverviewElement.appendChild(createOverviewDiv( + "icon-chart-bar", window.i18n[this.lang].home.overview.downloadsLastWeek, formattedNumber + )); } } catch { diff --git a/public/core/i18n.js b/public/core/i18n.js new file mode 100644 index 00000000..495b8c48 --- /dev/null +++ b/public/core/i18n.js @@ -0,0 +1,8 @@ +// Import Third-party Dependencies +import { getJSON } from "@nodesecure/vis-network"; + +export class i18n { + async fetch() { + return await getJSON(`/i18n`); + } +} diff --git a/public/main.js b/public/main.js index 86a64b5b..7afe26e9 100644 --- a/public/main.js +++ b/public/main.js @@ -12,12 +12,19 @@ import { Locker } from "./components/locker/locker.js"; // Import Views Components import { Settings } from "./components/views/settings/settings.js"; import { HomeView } from "./components/views/home/home.js"; + +// Import Core Components import { NetworkNavigation } from "./core/network-navigation.js"; +import { i18n } from "./core/i18n.js"; + +// Import Utils +import * as utils from "./common/utils.js"; document.addEventListener("DOMContentLoaded", async() => { window.locker = null; window.popup = new Popup(); window.settings = await new Settings().fetchUserConfig(); + window.i18n = await new i18n().fetch(); window.navigation = new ViewNavigation(); window.wiki = new Wiki(); let packageInfoOpened = false; @@ -31,7 +38,7 @@ document.addEventListener("DOMContentLoaded", async() => { // Initialize vis Network NodeSecureNetwork.networkElementId = "dependency-graph"; - const nsn = new NodeSecureNetwork(secureDataSet); + const nsn = new NodeSecureNetwork(secureDataSet, { i18n: window.i18n[utils.currentLang()] }); window.locker = new Locker(nsn); new HomeView(secureDataSet, nsn); @@ -80,7 +87,7 @@ document.addEventListener("DOMContentLoaded", async() => { if (networkNavigation.currentNodeParams !== null) { window.navigation.setNavByName("network--view"); - nsn.neighbourHighlight(networkNavigation.currentNodeParams); + nsn.neighbourHighlight(networkNavigation.currentNodeParams, window.i18n[utils.currentLang()]); updateShowInfoMenu(networkNavigation.currentNodeParams); } }); diff --git a/src/http-server/endpoints/i18n.js b/src/http-server/endpoints/i18n.js new file mode 100644 index 00000000..660181fb --- /dev/null +++ b/src/http-server/endpoints/i18n.js @@ -0,0 +1,10 @@ +// Import Third-party Dependencies +import send from "@polka/send-type"; + +// Import Internal Dependencies +import english from "../../../i18n/english.js"; +import french from "../../../i18n/french.js"; + +export async function get(req, res) { + send(res, 200, { english: english.ui, french: french.ui }); +} diff --git a/src/http-server/index.js b/src/http-server/index.js index 7e41cd35..5f7c02b6 100644 --- a/src/http-server/index.js +++ b/src/http-server/index.js @@ -15,6 +15,7 @@ import * as config from "./endpoints/config.js"; import * as bundle from "./endpoints/bundle.js"; import * as npmDownloads from "./endpoints/npm-downloads.js"; import * as scorecard from "./endpoints/ossf-scorecard.js"; +import * as locali18n from "./endpoints/i18n.js"; import * as middleware from "./middleware.js"; export function buildServer(dataFilePath, options = {}) { @@ -32,6 +33,7 @@ export function buildServer(dataFilePath, options = {}) { httpServer.get("/data", data.get); httpServer.get("/config", config.get); httpServer.put("/config", config.save); + httpServer.get("/i18n", locali18n.get); httpServer.get("/flags", flags.getAll); httpServer.get("/flags/description/:title", flags.get); diff --git a/test/httpServer.test.js b/test/httpServer.test.js index 599983c5..f3e69163 100644 --- a/test/httpServer.test.js +++ b/test/httpServer.test.js @@ -264,6 +264,14 @@ describe("httpServer", () => { }); }); + test("GET '/i18n' should return i18n", async() => { + const result = await get(new URL("/i18n", HTTP_URL)); + assert.equal(result.statusCode, 200); + + const keys = Object.keys(result.data); + assert.deepEqual(keys, ["english", "french"]); + }); + test("'/download/:pkgName' should return package downloads", async() => { const result = await get(new URL("/downloads/fastify", HTTP_URL)); diff --git a/views/index.html b/views/index.html index 9107536d..d55294d8 100644 --- a/views/index.html +++ b/views/index.html @@ -150,7 +150,7 @@
-

UNLOCKED

+

[[=z.token('network.unlocked')]]