Skip to content
This repository was archived by the owner on Apr 21, 2025. It is now read-only.

Commit bb8c6e6

Browse files
committed
add translation key ci check
1 parent 9db53cc commit bb8c6e6

File tree

9 files changed

+1164
-3
lines changed

9 files changed

+1164
-3
lines changed

.eslintrc.cjs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ module.exports = {
2020
rules: {
2121
"@typescript-eslint/no-var-requires": "off" // Disable this specific rule for CJS files
2222
}
23-
}
23+
},
24+
{
25+
files: ['src/i18n/**/translations.ts'],
26+
rules: {
27+
"internal-rules/check-i18n-keys": "off" //Disabled so no warnings are presented everytime pre-commit is run
28+
}
29+
},
2430
],
2531
parser: "@typescript-eslint/parser",
2632
parserOptions: {
@@ -32,7 +38,7 @@ module.exports = {
3238
jsx: true
3339
}
3440
},
35-
plugins: ["@typescript-eslint", "solid", "import"],
41+
plugins: ["@typescript-eslint", "solid", "import", "internal-rules"],
3642
rules: {
3743
"@typescript-eslint/no-unused-vars": [
3844
"warn",

justfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ dev:
66
pre:
77
pnpm run pre-commit
88

9+
i18n LANG:
10+
#!/bin/bash
11+
pnpm eslint-path "./src/i18n/{{LANG}}/translations.ts" --rule "{internal-rules/check-i18n-keys: warn}"
12+
913
local:
1014
pnpm install && pnpm link --global "@mutinywallet/mutiny-wasm"
1115

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
"build": "tsc && vite build",
99
"check-types": "tsc --noemit",
1010
"eslint": "eslint src",
11-
"pre-commit": "pnpm run eslint && pnpm run check-types && pnpm run format",
11+
"eslint-path": "eslint",
12+
"check-i18n-keys": "eslint src/i18n/**/translations.ts --rule \"{internal-rules/check-i18n-keys: warn}\"",
13+
"pre-commit": "pnpm eslint && pnpm run check-types && pnpm run format",
1214
"format": "prettier --write \"{.,src/**,e2e/**}/*.{ts,tsx,js,jsx,json,css,scss,md}\"",
1315
"check-format": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,scss,md}\""
1416
},
@@ -26,6 +28,7 @@
2628
"eslint": "^8.52.0",
2729
"eslint-import-resolver-typescript": "2.7.1",
2830
"eslint-plugin-import": "2.27.5",
31+
"eslint-plugin-internal-rules": "file:tools/internal-rules",
2932
"eslint-plugin-prettier": "4.2.1",
3033
"eslint-plugin-solid": "0.13.0",
3134
"postcss": "^8.4.31",

pnpm-lock.yaml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/internal-rules/.eslintrc.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use strict";
2+
3+
module.exports = {
4+
root: true,
5+
extends: [
6+
"eslint:recommended",
7+
"plugin:eslint-plugin/recommended",
8+
"plugin:node/recommended"
9+
],
10+
env: {
11+
node: true
12+
},
13+
overrides: [
14+
{
15+
files: ["tests/**/*.js"],
16+
env: { mocha: true }
17+
}
18+
]
19+
};

tools/internal-rules/lib/index.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @fileoverview internal eslint rules
3+
* @author
4+
*/
5+
"use strict";
6+
7+
//------------------------------------------------------------------------------
8+
// Requirements
9+
//------------------------------------------------------------------------------
10+
11+
const requireIndex = require("requireindex");
12+
13+
//------------------------------------------------------------------------------
14+
// Plugin Definition
15+
//------------------------------------------------------------------------------
16+
17+
// import all rules in lib/rules
18+
module.exports.rules = requireIndex(__dirname + "/rules");
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"use strict";
2+
3+
const fs = require("fs");
4+
const path = require("path");
5+
const { parse } = require("@babel/parser");
6+
7+
module.exports = {
8+
meta: {
9+
name: "check-i18n-keys",
10+
type: "suggestion",
11+
docs: {
12+
description:
13+
"Ensure translation keys in other language files match the keys in the English translation file.",
14+
category: "Best Practices",
15+
recommended: true
16+
},
17+
fixable: null,
18+
schema: []
19+
},
20+
create: function (context) {
21+
function extractKeys(node, parentKey = "") {
22+
const keys = [];
23+
let properties = node.properties;
24+
25+
if (typeof node === "string") {
26+
const fileContent = fs.readFileSync(node, "utf8");
27+
const ast = parse(fileContent, {
28+
sourceType: "module",
29+
plugins: ["typescript", "jsx"]
30+
});
31+
properties =
32+
!!ast && ast.program.body[0].declaration.properties;
33+
}
34+
35+
function traverseProperties(properties, parentKey) {
36+
properties.forEach((property) => {
37+
if (
38+
(property.type === "ObjectProperty" ||
39+
property.type === "Property") &&
40+
property.key.type === "Identifier"
41+
) {
42+
const currentKey = parentKey
43+
? `${parentKey}.${property.key.name}`
44+
: property.key.name;
45+
keys.push(currentKey);
46+
if (property.value.type === "ObjectExpression") {
47+
traverseProperties(
48+
property.value.properties,
49+
currentKey
50+
);
51+
}
52+
}
53+
});
54+
}
55+
56+
traverseProperties(properties, parentKey);
57+
58+
return keys;
59+
}
60+
61+
return {
62+
Program(node) {
63+
for (const statement of node.body) {
64+
const fallbackFilePath = path
65+
.relative(process.cwd(), context.getFilename())
66+
.replace(
67+
/\/i18n\/\w+\/translations\.ts$/,
68+
"/i18n/en/translations.ts"
69+
);
70+
71+
const keys = extractKeys(statement.declaration);
72+
73+
const enKeys = extractKeys(fallbackFilePath);
74+
75+
// Report missing keys
76+
enKeys.forEach((enKey) => {
77+
if (!keys.includes(enKey)) {
78+
context.report({
79+
node: node,
80+
message: `missing key '${enKey}'`
81+
});
82+
}
83+
});
84+
85+
// Report extra keys
86+
keys.forEach((key) => {
87+
if (!enKeys.includes(key)) {
88+
context.report({
89+
node: node,
90+
message: `extra key '${key}'`
91+
});
92+
}
93+
});
94+
}
95+
}
96+
};
97+
}
98+
};

tools/internal-rules/package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "eslint-plugin-internal-rules",
3+
"version": "0.0.0",
4+
"description": "internal eslint rules",
5+
"keywords": [
6+
"eslint",
7+
"eslintplugin",
8+
"eslint-plugin"
9+
],
10+
"author": "",
11+
"main": "./lib/index.js",
12+
"exports": "./lib/index.js",
13+
"scripts": {
14+
"lint": "npm-run-all \"lint:*\"",
15+
"lint:js": "eslint ."
16+
},
17+
"dependencies": {
18+
"requireindex": "^1.2.0"
19+
},
20+
"devDependencies": {
21+
"eslint": "^8.19.0",
22+
"eslint-plugin-eslint-plugin": "^5.0.0",
23+
"eslint-plugin-node": "^11.1.0"
24+
},
25+
"engines": {
26+
"node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
27+
},
28+
"peerDependencies": {
29+
"eslint": ">=7"
30+
},
31+
"license": "ISC"
32+
}

0 commit comments

Comments
 (0)