130 lines
3.7 KiB
JavaScript
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)
|
|
},
|
|
})
|
|
}
|
|
},
|
|
}
|
|
},
|
|
},
|
|
},
|
|
}
|