Folo/plugins/eslint/eslint-check-i18n-json.js

130 lines
3.7 KiB
JavaScript

// @ts-check
/** @type {import("eslint").ESLint.Plugin} */
import fs from "node:fs"
import path from "node:path"
export default {
rules: {
"valid-i18n-keys": {
meta: {
type: "problem",
docs: {
description: "Ensure i18n JSON keys are flat and valid as object paths",
category: "Possible Errors",
recommended: true,
},
fixable: null,
},
create(context) {
return {
Program(node) {
const { filename, sourceCode } = context
if (!filename.endsWith(".json")) return
let json
try {
json = JSON.parse(sourceCode.text)
} catch {
context.report({
node,
message: "Invalid JSON format",
})
return
}
const keys = Object.keys(json)
const keyPrefixes = new Set()
for (const key of keys) {
if (key.includes(".")) {
const parts = key.split(".")
for (let i = 1; i < parts.length; i++) {
const prefix = parts.slice(0, i).join(".")
if (keys.includes(prefix)) {
context.report({
node,
message: `Invalid key structure: '${key}' conflicts with '${prefix}'`,
})
}
keyPrefixes.add(prefix)
}
}
}
for (const key of keys) {
if (keyPrefixes.has(key)) {
context.report({
node,
message: `Invalid key structure: '${key}' is a prefix of another key`,
})
}
}
},
}
},
},
"no-extra-keys": {
meta: {
type: "problem",
docs: {
description: "Ensure non-English JSON files don't have extra keys not present in en.json",
category: "Possible Errors",
recommended: true,
},
fixable: "code", // 将 fixable 设置为 "code"
},
create(context) {
return {
Program(node) {
const { filename, sourceCode } = context
if (!filename.endsWith(".json")) return
const parts = filename.split(path.sep)
const lang = parts.at(-1).split(".")[0]
const namespace = parts.at(-2)
if (lang === "en") return
let currentJson = {}
let englishJson = {}
try {
currentJson = JSON.parse(sourceCode.text)
const englishFilePath = path.join(process.cwd(), "locales", namespace, "en.json")
englishJson = JSON.parse(fs.readFileSync(englishFilePath, "utf8"))
} catch (error) {
context.report({
node,
message: `Error parsing JSON: ${error.message}`,
})
return
}
const extraKeys = Object.keys(currentJson).filter(
(key) => !Object.prototype.hasOwnProperty.call(englishJson, key),
)
for (const key of extraKeys) {
context.report({
node,
message: `Key "${key}" is present in ${lang}.json but not in en.json for namespace "${namespace}"`,
fix(fixer) {
const newJson = Object.fromEntries(
Object.entries(currentJson).filter(([k]) => !extraKeys.includes(k)),
)
const newText = `${JSON.stringify(newJson, null, 2)}\n`
return fixer.replaceText(node, newText)
},
})
}
},
}
},
},
},
}