From f44ba01b8e1e0cdfe2f11f793617cc719f03adf1 Mon Sep 17 00:00:00 2001 From: lapatric <42653152+lapatric@users.noreply.github.com> Date: Tue, 17 Jun 2025 01:36:52 +0200 Subject: [PATCH 1/3] [DEV-4083] custom eslint error for translations --- .eslintrc.js | 1 + eslint-rules/validate-translation-keys.js | 76 +++++++++++++++++++++++ package.json | 4 +- 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 eslint-rules/validate-translation-keys.js diff --git a/.eslintrc.js b/.eslintrc.js index 848ffd3e3..e3a36d5fa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,7 @@ module.exports = { }, ignorePatterns: ['.eslintrc.js'], rules: { + 'validate-translation-keys': 'error', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/eslint-rules/validate-translation-keys.js b/eslint-rules/validate-translation-keys.js new file mode 100644 index 000000000..46b2fc370 --- /dev/null +++ b/eslint-rules/validate-translation-keys.js @@ -0,0 +1,76 @@ +const fs = require('fs'); +const path = require('path'); + +function loadKeys(filePath) { + try { + const data = fs.readFileSync(filePath, 'utf8'); + const json = JSON.parse(data); + const keys = new Set(); + + function recurse(obj, prefix = '') { + for (const [k, v] of Object.entries(obj)) { + const full = prefix ? `${prefix}.${k}` : k; + if (v && typeof v === 'object') { + recurse(v, full); + } else { + keys.add(full); + } + } + } + recurse(json); + return keys; + } catch (error) { + console.warn(`Could not load translation file: ${filePath}`); + return new Set(); + } +} + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Ensure translation keys exist in DE/FR/IT', + category: 'Possible Errors', + }, + schema: [], + messages: { + missingTranslation: 'Translation key "{{key}}" missing in: {{languages}}', + invalidKey: 'Translation key "{{key}}" not found in any translation file', + }, + }, + + create(context) { + const root = context.getCwd(); + const langs = { + German: loadKeys(path.join(root, 'src/translations/languages/de.json')), + French: loadKeys(path.join(root, 'src/translations/languages/fr.json')), + Italian: loadKeys(path.join(root, 'src/translations/languages/it.json')), + }; + + return { + CallExpression(node) { + if ( + node.callee.name === 'translate' && + node.arguments.length >= 2 && + node.arguments[0].type === 'Literal' && + node.arguments[1].type === 'Literal' + ) { + const section = node.arguments[0].value; + const key = node.arguments[1].value; + const fullKey = `${section}.${key}`; + + const missing = Object.entries(langs) + .filter(([, set]) => !set.has(fullKey)) + .map(([name]) => name); + + const reportData = { key: fullKey, languages: missing.join(', ') }; + if (missing.length === 3) { + context.report({ node: node.arguments[1], messageId: 'invalidKey', data: reportData }); + } else if (missing.length > 0) { + context.report({ node: node.arguments[1], messageId: 'missingTranslation', data: reportData }); + } + } + }, + }; + }, +}; diff --git a/package.json b/package.json index d1c57f563..9b6270781 100644 --- a/package.json +++ b/package.json @@ -64,8 +64,8 @@ "widget": "cp src/index.tsx src/index.bak.tsx && cp src/index-widget.tsx src/index.tsx && env-cmd -f .env.prd env-cmd -f .env.widget react-app-rewired build && mv src/index.bak.tsx src/index.tsx", "widget:dev": "cp src/index.tsx src/index.bak.tsx && cp src/index-widget.tsx src/index.tsx && env-cmd -f .env.dev env-cmd -f .env.widget react-app-rewired build && mv src/index.bak.tsx src/index.tsx", "widget:loc": "cp src/index.tsx src/index.bak.tsx && cp src/index-widget.tsx src/index.tsx && env-cmd -f .env.loc env-cmd -f .env.widget react-app-rewired build && mv src/index.bak.tsx src/index.tsx", - "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --no-fix", - "lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "lint": "eslint \"{src,apps,libs,test}/**/*.{ts,tsx}\" --rulesdir eslint-rules --no-fix", + "lint:fix": "eslint \"{src,apps,libs,test}/**/*.{ts,tsx}\" --rulesdir eslint-rules --fix", "test": "react-app-rewired test --watchAll=false --passWithNoTests", "eject": "react-scripts eject", "serve": "serve build -l 4000", From cb0af18f215f3e2f1407fbd8d970662b7deb6647 Mon Sep 17 00:00:00 2001 From: lapatric <42653152+lapatric@users.noreply.github.com> Date: Thu, 7 Aug 2025 17:58:15 +0200 Subject: [PATCH 2/3] [DEV-4083] improvements --- .eslintrc.js | 2 +- eslint-rules/validate-translation-keys.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index e3a36d5fa..c946b7f16 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,7 +16,7 @@ module.exports = { }, ignorePatterns: ['.eslintrc.js'], rules: { - 'validate-translation-keys': 'error', + 'validate-translation-keys': 'warn', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', diff --git a/eslint-rules/validate-translation-keys.js b/eslint-rules/validate-translation-keys.js index 46b2fc370..596f70fb2 100644 --- a/eslint-rules/validate-translation-keys.js +++ b/eslint-rules/validate-translation-keys.js @@ -25,6 +25,13 @@ function loadKeys(filePath) { } } +// Load translations once at module level for performance +const langs = { + German: loadKeys(path.join(process.cwd(), 'src/translations/languages/de.json')), + French: loadKeys(path.join(process.cwd(), 'src/translations/languages/fr.json')), + Italian: loadKeys(path.join(process.cwd(), 'src/translations/languages/it.json')), +}; + module.exports = { meta: { type: 'problem', @@ -40,13 +47,6 @@ module.exports = { }, create(context) { - const root = context.getCwd(); - const langs = { - German: loadKeys(path.join(root, 'src/translations/languages/de.json')), - French: loadKeys(path.join(root, 'src/translations/languages/fr.json')), - Italian: loadKeys(path.join(root, 'src/translations/languages/it.json')), - }; - return { CallExpression(node) { if ( From e650d29e83756df1ac88ad6322701189d816f7cb Mon Sep 17 00:00:00 2001 From: lapatric <42653152+lapatric@users.noreply.github.com> Date: Thu, 7 Aug 2025 18:01:02 +0200 Subject: [PATCH 3/3] [DEV-4083] cleanup --- eslint-rules/validate-translation-keys.js | 1 - 1 file changed, 1 deletion(-) diff --git a/eslint-rules/validate-translation-keys.js b/eslint-rules/validate-translation-keys.js index 596f70fb2..782613d28 100644 --- a/eslint-rules/validate-translation-keys.js +++ b/eslint-rules/validate-translation-keys.js @@ -25,7 +25,6 @@ function loadKeys(filePath) { } } -// Load translations once at module level for performance const langs = { German: loadKeys(path.join(process.cwd(), 'src/translations/languages/de.json')), French: loadKeys(path.join(process.cwd(), 'src/translations/languages/fr.json')),