diff --git a/README.md b/README.md index 31b4d22..90b74bd 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ export function getLanguages(): Promise; export function taggedString(str: string, ...keys: any[]): (...keys: any[]) => string; export function extend(language: string, tokens: Record): void; export function extendFromSystemPath(path: string): Promise; +export function getAllTranslations(): Promise; ``` > [!NOTE] @@ -80,6 +81,10 @@ You can generate a static webpage by using the `build:documentation` npm script: $ npm run build:documentation ``` +> [!WARNING] +> Make sure to fetch the translations from the `scanner` and `CLI` projects locally using the following npm script: `npm run fetch:i18n`. + + ## Contributing Feel free to add a new language. You need to take inspiration from the two supported languages and replicate the same keys. diff --git a/externalLanguages/cli/english.js b/externalLanguages/cli/english.js new file mode 100644 index 0000000..cee12a4 --- /dev/null +++ b/externalLanguages/cli/english.js @@ -0,0 +1,191 @@ +/* eslint-disable max-len */ +// Import Third-party Dependencies +import { taggedString as tS } from "../../src/utils.js"; + +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" + } +}; + +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" + }, + report: { + title: "Generate a report", + form: { + title: "Report title", + includesAllDeps: "Include all dependencies", + dark_theme: "Dark theme", + light_theme: "Light theme", + submit: "Generate" + } + } + }, + home: { + overview: { + title: "Overview", + dependencies: "dependencies", + totalSize: "total size", + directDeps: "direct deps", + transitiveDeps: "transitive deps", + downloadsLastWeek: "downloads last week", + generateReport: "Generate a report" + }, + watch: "Packages in the dependency tree requiring greater attention", + criticalWarnings: "Critical Warnings", + maintainers: "Maintainers", + showMore: "show more", + showLess: "show less" + }, + settings: { + general: { + title: "General", + save: "save", + defaultPannel: "Default Package Menu", + warnings: "SAST Warnings to ignore", + flags: "Flags (emojis) to ignore" + }, + shortcuts: { + title: "Shortcuts", + blockquote: "Click on hotkey to update", + goto: "Goto", + 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" + } +}; + +export default { cli, ui }; diff --git a/externalLanguages/cli/french.js b/externalLanguages/cli/french.js new file mode 100644 index 0000000..f26c633 --- /dev/null +++ b/externalLanguages/cli/french.js @@ -0,0 +1,191 @@ +/* eslint-disable max-len */ +// Import Third-party Dependencies +import { taggedString as tS } from "../../src/utils.js"; + +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" + } +}; + +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" + }, + report: { + title: "Générer un rapport", + form: { + title: "Titre du rapport", + includesAllDeps: "Inclure toutes les dépendances", + dark_theme: "Thème sombre", + light_theme: "Thème clair", + submit: "Générer" + } + } + }, + home: { + overview: { + 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", + generateReport: "Générer un rapport" + }, + watch: "Packages dans l'arbre de dépendance nécessitant une plus grande attention", + criticalWarnings: "Avertissements critiques", + maintainers: "Mainteneurs", + showMore: "voir plus", + showLess: "voir moins" + }, + settings: { + general: { + title: "Général", + save: "sauvegarder", + defaultPannel: "Panneau par défaut", + warnings: "Avertissements à ignorer", + flags: "Drapeau (emojis) à ignorer" + }, + shortcuts: { + title: "Raccourcis", + blockquote: "Cliquer sur le raccourci clavier pour mettre à jour", + goto: "Ouvrir", + 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" + } +}; + +export default { cli, ui }; diff --git a/externalLanguages/scanner/english.js b/externalLanguages/scanner/english.js new file mode 100644 index 0000000..a237856 --- /dev/null +++ b/externalLanguages/scanner/english.js @@ -0,0 +1,6 @@ +const scanner = { + disable_scarf: "This dependency could collect data against your consent so think to disable it with the env var: SCARF_ANALYTICS", + keylogging: "This dependency can retrieve your keyboard and mouse inputs. It can be used for 'keylogging' attacks/malwares." +}; + +export default { scanner }; diff --git a/externalLanguages/scanner/french.js b/externalLanguages/scanner/french.js new file mode 100644 index 0000000..c69da52 --- /dev/null +++ b/externalLanguages/scanner/french.js @@ -0,0 +1,7 @@ +const scanner = { + disable_scarf: "Cette dépendance peut récolter des données contre votre volonté, pensez donc à la désactiver en fournissant la variable d'environnement SCARF_ANALYTICS", + keylogging: "Cette dépendance peut obtenir vos entrées clavier ou de souris. Cette dépendance peut être utilisée en tant que 'keylogging' attacks/malwares." +}; + +export default { scanner }; + diff --git a/index.d.ts b/index.d.ts index e7e83f6..d67fb75 100644 --- a/index.d.ts +++ b/index.d.ts @@ -9,6 +9,7 @@ declare namespace i18n { export function taggedString(str: string | TemplateStringsArray, ...keys: any[]): (...keys: any[]) => string; export function extend(language: string, tokens: Record): void; export function extendFromSystemPath(path: string): Promise; + export function getAllTranslations(): Promise; } export = i18n; diff --git a/index.js b/index.js index 8e47e72..cca7a50 100644 --- a/index.js +++ b/index.js @@ -113,3 +113,7 @@ export async function extendFromSystemPath(languagesDirPath) { } } } + +export async function getAllTranslations() { + return languages; +} diff --git a/languages/english.js b/languages/english.js index b84453b..e48d224 100644 --- a/languages/english.js +++ b/languages/english.js @@ -1,6 +1,8 @@ /* eslint-disable max-len */ // Require Internal Dependencies +import cli from "../externalLanguages/cli/english.js"; +import scanner from "../externalLanguages/scanner/english.js"; import { taggedString as tS } from "../src/utils.js"; const lang = "en"; @@ -39,4 +41,4 @@ const sast_warnings = { zeroSemVer: "Semantic version starting with 0.x (unstable project or without serious versioning)" }; -export const english = { lang, depWalker, warnings, sast_warnings }; +export const english = { lang, depWalker, warnings, sast_warnings, cli, scanner }; diff --git a/languages/french.js b/languages/french.js index 1202486..3380884 100644 --- a/languages/french.js +++ b/languages/french.js @@ -1,6 +1,8 @@ /* eslint-disable max-len */ // Import Internal Dependencies +import cli from "../externalLanguages/cli/french.js"; +import scanner from "../externalLanguages/scanner/french.js"; import { taggedString as tS } from "../src/utils.js"; const lang = "fr"; @@ -41,4 +43,4 @@ const sast_warnings = { zeroSemVer: "Version sémantique commençant par 0.x (projet instable ou sans versionnement sérieux)" }; -export const french = { lang, depWalker, warnings, sast_warnings }; +export const french = { lang, depWalker, warnings, sast_warnings, cli, scanner }; diff --git a/package.json b/package.json index b0f7f71..c5dd538 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "test-only": "node --test test/", "test": "npm run lint && npm run test-only", "coverage": "c8 -r html npm test", - "build:documentation": "node ./scripts/buildDocumentation.js" + "build:documentation": "node ./scripts/buildDocumentation.js", + "fetch:i18n": "node ./scripts/fetchi18n.js" }, "repository": { "type": "git", @@ -32,12 +33,13 @@ "homepage": "https://github.com/NodeSecure/i18n#readme", "devDependencies": { "@nodesecure/eslint-config": "^1.8.0", + "@nodesecure/github": "^1.2.0", "c8": "^10.1.2", "zup": "^0.0.2" }, "type": "module", "engines": { - "node": ">=18" + "node": "^20.12.0 || >=21.7.0" }, "dependencies": { "cacache": "^18.0.0", diff --git a/scripts/fetchi18n.js b/scripts/fetchi18n.js new file mode 100644 index 0000000..0c02520 --- /dev/null +++ b/scripts/fetchi18n.js @@ -0,0 +1,47 @@ +#!/usr/bin/env node + +// Import Node.js Dependencies +import fs from "node:fs"; +import path from "node:path"; +import { styleText } from "node:util"; + +// Import Third-party Dependencies +import * as github from "@nodesecure/github"; + +const repos = { + scanner: "workspaces/scanner/src/i18n", + cli: "i18n" +}; + +for (const [repo, i18nFolderPath] of Object.entries(repos)) { + const { location } = await github.downloadAndExtract(`NodeSecure.${repo}`, { branch: "master" }); + const i18nPath = path.join(location, i18nFolderPath); + + console.log(styleText(["bold", "green"], `[INFO]`), `i18nPath: ${i18nPath}`); + + const french = fs.readFileSync(path.join(i18nPath, "french.js"), "utf-8"); + const english = fs.readFileSync(path.join(i18nPath, "english.js"), "utf-8"); + + const frenchFilePath = path.join(process.cwd(), "externalLanguages", repo, "french.js"); + const englishFilePath = path.join(process.cwd(), "externalLanguages", repo, "english.js"); + + fs.mkdirSync(path.dirname(frenchFilePath), { recursive: true }); + fs.mkdirSync(path.dirname(englishFilePath), { recursive: true }); + + if (repo === "cli") { + fs.writeFileSync(frenchFilePath, french.replace( + "import { taggedString as tS } from \"@nodesecure/i18n\";", + "import { taggedString as tS } from \"../../src/utils.js\";" + )), + fs.writeFileSync(englishFilePath, english.replace( + "import { taggedString as tS } from \"@nodesecure/i18n\";", + "import { taggedString as tS } from \"../../src/utils.js\";" + )); + } else { + fs.writeFileSync(frenchFilePath, french); + fs.writeFileSync(englishFilePath, english); + } + + fs.rmSync(location, { recursive: true, force: true }); +} +