From 56d60d78665d6d0d81245e6d7eeb5587eb839d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 12 Aug 2025 14:09:36 +0200 Subject: [PATCH] Finalized locale check script --- .../static-analysis/fallbackLocale.js | 92 -------------- .../workflows/static-analysis/locale/index.js | 115 ++++++++++++++++++ .../static-analysis/locale/package.json | 13 ++ 3 files changed, 128 insertions(+), 92 deletions(-) delete mode 100644 .github/workflows/static-analysis/fallbackLocale.js create mode 100644 .github/workflows/static-analysis/locale/index.js create mode 100644 .github/workflows/static-analysis/locale/package.json diff --git a/.github/workflows/static-analysis/fallbackLocale.js b/.github/workflows/static-analysis/fallbackLocale.js deleted file mode 100644 index f4056195fd..0000000000 --- a/.github/workflows/static-analysis/fallbackLocale.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Look into all local files, and collect unique keys. - * Ensure fallback locale (English) has translation for all keys. - */ - -import { readdir, readFile } from "fs/promises"; -import { join } from "path"; - -const translationsPath = join( - __dirname, - "../../../app/config/locale/translations", -); -const fallbackLocale = "en.json"; - -async () => { - try { - const files = await readdir(translationsPath).filter((file) => - file.endsWith(".json"), - ); - - if (files.length === 0) { - console.error("No translation files found in ", translationsPath); - process.exit(1); - } - - // Check if fallback locale exists - if (!files.includes(fallbackLocale)) { - console.error(`Fallback locale file ${fallbackLocale} not found`); - process.exit(1); - } - - // Collect all unique keys from all translation files - const allKeys = new Set(); - - for (const file of files) { - const filePath = join(translationsPath, file); - const content = await readFile(filePath, "utf8"); - const translations = JSON.parse(content); - - // Add all keys from this file - Object.keys(translations).forEach((key) => allKeys.add(key)); - } - - // Read fallback locale - const fallbackPath = join(translationsPath, fallbackLocale); - const fallbackContent = await readFile(fallbackPath, "utf8"); - const fallbackTranslations = JSON.parse(fallbackContent); - - // Check for missing keys in fallback locale - const missingKeys = []; - const fallbackKeys = new Set(Object.keys(fallbackTranslations)); - - for (const key of allKeys) { - if (!fallbackKeys.has(key)) { - missingKeys.push(key); - } - } - - // Report results - console.log( - `Found ${files.length} translation files in ${translationsPath}`, - ); - console.log(`Total unique keys found across all locales: ${allKeys.size}`); - console.log( - `Keys in fallback locale (${fallbackLocale}): ${fallbackKeys.size}`, - ); - - if (missingKeys.length > 0) { - console.error( - `\nERROR: Fallback locale (${fallbackLocale}) is missing ${missingKeys.length} key(s):`, - ); - missingKeys.sort().forEach((key) => { - console.error(` - ${key}`); - }); - console.error( - `\nTo fix this issue, add the missing keys to ${translationsPath}/${fallbackLocale}`, - ); - process.exit(1); - } else { - console.log( - `\nSUCCESS: Fallback locale (${fallbackLocale}) contains all required keys.`, - ); - console.log( - `All ${allKeys.size} translation keys are present in the fallback locale.`, - ); - process.exit(0); - } - } catch (error) { - console.error("Unexpected error: ", error.message); - process.exit(1); - } -}; diff --git a/.github/workflows/static-analysis/locale/index.js b/.github/workflows/static-analysis/locale/index.js new file mode 100644 index 0000000000..dd4594fc4d --- /dev/null +++ b/.github/workflows/static-analysis/locale/index.js @@ -0,0 +1,115 @@ +/* + * Look into all local files, and collect unique keys. + * Ensure fallback locale (English) has translation for all keys. + * If configured as `const strict = true`, all locales will be checked to include all keys. + */ + +import { readdir, readFile } from "fs/promises"; +import { join, dirname } from "path"; +import { fileURLToPath } from "url"; + +const config = { + strict: false, + fallbackLocale: "en.json", +}; + +(async () => { + try { + // Prepare current directory equivalent in ES modules + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + + const translationsPath = join( + __dirname, + "../../../../app/config/locale/translations", + ); + + const files = (await readdir(translationsPath)).filter((file) => + file.endsWith(".json"), + ); + + if (files.length === 0) { + console.error("No translation files found in ", translationsPath); + process.exit(1); + } + + // Check if fallback locale exists + if (!files.includes(config.fallbackLocale)) { + console.error(`Fallback locale file ${config.fallbackLocale} not found`); + process.exit(1); + } + + console.log( + `Found ${files.length} translation files in ${translationsPath}`, + ); + + // Collect all unique keys from all translation files + const allKeys = new Set(); + + for (const file of files) { + const filePath = join(translationsPath, file); + const content = await readFile(filePath, "utf8"); + const translations = JSON.parse(content); + + // Add all keys from this file + Object.keys(translations).forEach((key) => allKeys.add(key)); + } + + console.log(`Total unique keys found across all locales: ${allKeys.size}`); + + const localesToCheck = []; + if (config.strict) { + localesToCheck.push(...files); + } else { + localesToCheck.push(config.fallbackLocale); + } + + let errorsCount = 0; + let missingLocaleCount = 0; + + for (const localeToCheck of localesToCheck) { + // Read locale + const path = join(translationsPath, localeToCheck); + const content = await readFile(path, "utf8"); + const translations = JSON.parse(content); + + // Check for missing keys in the locale + const keys = new Set(Object.keys(translations)); + console.log(`Keys in locale (${localeToCheck}): ${keys.size}`); + + const missingKeys = []; + for (const key of allKeys) { + if (!keys.has(key)) { + missingKeys.push(key); + } + } + + if (missingKeys.length > 0) { + console.error( + `\nERROR: Fallback locale (${localeToCheck}) is missing ${missingKeys.length} key(s):`, + ); + missingKeys.sort().forEach((key) => { + console.error(` - ${key}`); + }); + console.error( + `\nTo fix this issue, add the missing keys to ${translationsPath}/${localeToCheck}`, + ); + errorsCount++; + missingLocaleCount += missingKeys.length; + } else { + console.log( + `\nSUCCESS: Fallback locale (${localeToCheck}) contains all ${allKeys.size} keys.`, + ); + } + } + + if (errorsCount > 0) { + console.log(`\n${missingLocaleCount} locales missing found across ${errorsCount} locales.`); + process.exit(1); + } + } catch (error) { + console.error("Unexpected error."); + console.error(error); + process.exit(1); + } +})(); diff --git a/.github/workflows/static-analysis/locale/package.json b/.github/workflows/static-analysis/locale/package.json new file mode 100644 index 0000000000..748a3e6d2a --- /dev/null +++ b/.github/workflows/static-analysis/locale/package.json @@ -0,0 +1,13 @@ +{ + "name": "static-analysis-locale", + "version": "1.0.0", + "type": "module", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +}