From 3f27608850b3a5d8adc7fad37f766e774bd58502 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Wed, 20 Mar 2024 12:00:56 +0000 Subject: [PATCH] Add a script to check our translation files are valid Checks the following: - Translation files are valid JS - Each translation file is registered in translations.js - Each translation's code matches its name - Translation dictionaries only contain keys that exist in the English translation. --- .github/workflows/check-translations.yml | 28 ++++++++ package.json | 3 +- tools/check-translations.js | 83 ++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/check-translations.yml create mode 100644 tools/check-translations.js diff --git a/.github/workflows/check-translations.yml b/.github/workflows/check-translations.yml new file mode 100644 index 00000000..364328e9 --- /dev/null +++ b/.github/workflows/check-translations.yml @@ -0,0 +1,28 @@ +# This workflow runs the tools/check-translations.js script to make sure that the translation data is valid. + +name: Check Translations + +env: + NODE_VERSION: 21.x + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: ./package-lock.json + - run: npm ci + - run: npm run check-translations \ No newline at end of file diff --git a/package.json b/package.json index dd6871ae..f76793fa 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon --exec \"node dev-server.js\" ", - "build": "node ./build.js" + "build": "node ./build.js", + "check-translations": "node tools/check-translations.js" }, "nodemonConfig": { "ext": "js, json, mjs, jsx, svg, css", diff --git a/tools/check-translations.js b/tools/check-translations.js new file mode 100644 index 00000000..b718ec63 --- /dev/null +++ b/tools/check-translations.js @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import translations from '../src/i18n/translations/translations.js'; +import fs from 'fs'; + +let hadError = false; +function reportError(message) { + hadError = true; + process.stderr.write(`❌ ${message}\n`); +} + +// Check that each translation file is recorded in `translations` +async function checkTranslationRegistrations() { + const files = await fs.promises.readdir('./src/i18n/translations'); + for (const fileName of files) { + if (!fileName.endsWith('.js')) continue; + const translationName = fileName.substring(0, fileName.length - 3); + if (translationName === 'translations') continue; + + const translation = translations[translationName]; + if (!translation) { + reportError(`Translation '${translationName}' is not listed in translations.js, please add it!`); + continue; + } + + if (!translation.name) { + reportError(`Translation '${translationName}' is missing a name!`); + } + if (!translation.code) { + reportError(`Translation '${translationName}' is missing a code!`); + } else if (translation.code !== translationName) { + reportError(`Translation '${translationName}' has code '${translation.code}', which should be '${translationName}'!`); + } + if (typeof translation.dictionary !== 'object') { + reportError(`Translation '${translationName}' is missing a translations dictionary! Should be an object.`); + } + } +} + +function checkTranslationKeys() { + const enDictionary = translations.en.dictionary; + + for (const translation of Object.values(translations)) { + // We compare against the en translation, so checking it doesn't make sense. + if (translation.code === 'en') continue; + + // If the dictionary is missing, we already reported that in checkTranslationRegistrations(). + if (typeof translation.dictionary !== "object") continue; + + for (const [key, value] of Object.entries(translation.dictionary)) { + if (!enDictionary[key]) { + reportError(`Translation '${translation.code}' has key '${key}' that doesn't exist in 'en'!`); + } + } + } +} + +await checkTranslationRegistrations(); +checkTranslationKeys(); + +if (hadError) { + process.stdout.write('Errors were found in translation files.\n'); + process.exit(1); +} + +process.stdout.write('✅ Translations appear valid.\n'); +process.exit(0); \ No newline at end of file