检查多语言重复
大约 5 分钟
最近需要检查多语言文件中是否有重复的 key,于是写了一个 Node.js 脚本来实现这个功能。
这个脚本会在 git 提交代码时,对提交的多语言文件进行校验。通过遍历指定目录下的所有 JSON 文件,检查每个文件中的 key 是否有重复,并将结果输出到控制台。同时,脚本还会将所有 key 存入一个缓存文件中,以便下次运行时可以快速查询。
实现逻辑
理论依据:由于 lint-staged
会将暂存区中被修改的文件路径作为参数传入定义的脚本。这意味着可以在 lint-staged
中配置的命令中直接访问提交的文件列表,而不需要手动指定文件路径。
因此 const files = process.argv.slice(2);
就是获取提交的文件列表,然后通过 fs.readFileSync
读取文件内容,最后通过 getLastLevelKeys
获取文件中的所有 key,并检查是否有重复的 key。
1. 基本路径和目录初始化
const cacheFilePath = path.join(__dirname, 'temp/i18n_keys_cache.json')
const cacheDirPath = path.join(__dirname, '../src/i18n/zh') // 指定中文目录
cacheFilePath
定义缓存文件路径(用于保存之前检查过的键)。cacheDirPath
指定zh
文件夹路径,作为多语言 JSON 文件的存储目录,用于初始化缓存文件。
2. 创建缓存目录(如果不存在)
const cacheDir = path.dirname(cacheFilePath)
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true })
}
- 检查并创建存储缓存文件的
temp
目录,以确保缓存文件的存放位置存在。
3. 加载传入的文件路径列表
const files = process.argv.slice(2)
console.info('🚀 ~ Starting file check:', files)
process.argv.slice(2)
用于获取命令行传入的文件路径列表,排除 Node 和脚本本身的路径。files
数组将被传入后续检查函数。
4. 加载缓存并生成所有键的 Set 集合
function loadCache() {
if (fs.existsSync(cacheFilePath)) {
const cacheContent = fs.readFileSync(cacheFilePath, 'utf8')
const cache = JSON.parse(cacheContent)
return {
cache,
allKeysSet: new Set(Object.values(cache).flat()), // 将缓存的键存入 Set
}
}
const cache = initializeCache()
saveCache(cache)
return { cache, allKeysSet: new Set(Object.values(cache).flat()) }
}
- 检查缓存文件是否存在,如果存在则读取并解析 JSON 内容。
allKeysSet
是一个 Set 对象,用于高效存储并查询所有键,避免重复。- 若缓存不存在,通过
initializeCache()
初始化,并将所有的键添加到allKeysSet
。
5. 从指定目录初始化缓存
function initializeCache() {
const cache = {}
const files = fs
.readdirSync(cacheDirPath)
.filter((file) => file.endsWith('.json'))
files.forEach((file) => {
const fileName = path.basename(file, '.json')
const filePath = path.join(cacheDirPath, file)
const content = fs.readFileSync(filePath, 'utf8')
const jsonData = JSON.parse(content)
cache[fileName] = getLastLevelKeys(jsonData) // 获取 JSON 的最后一级键
})
console.info('🚀 ~ Initialized cache from zh directory with files:', files)
return cache
}
initializeCache()
遍历zh
目录下的所有 JSON 文件。- 对每个文件提取最后一级的键(用
getLastLevelKeys()
获取),并以文件名作为缓存中的键,便于定位和更新。
6. 获取 JSON 对象的最后一级键
function getLastLevelKeys(obj) {
const keys = []
function recurse(o) {
if (typeof o !== 'object' || o === null) return
for (const key in o) {
if (typeof o[key] === 'object') {
recurse(o[key])
} else {
keys.push(key)
}
}
}
recurse(obj)
return keys
}
getLastLevelKeys
是递归函数,遍历对象并提取其最底层的键,用于避免嵌套对象影响。
7. 检查重复键并更新缓存
function checkForDuplicateKeys(files) {
const { cache, allKeysSet } = loadCache()
const duplicateKeys = new Set()
files.forEach((file) => {
const fileName = path.basename(file, '.json')
const filePath = path.resolve(file)
const content = fs.readFileSync(filePath, 'utf8')
const jsonData = JSON.parse(content)
const keys = getLastLevelKeys(jsonData)
if (cache[fileName]) {
cache[fileName].forEach((key) => allKeysSet.delete(key))
delete cache[fileName]
}
let hasDuplicate = false
keys.forEach((key) => {
if (allKeysSet.has(key)) {
duplicateKeys.add(key)
hasDuplicate = true
}
})
if (!hasDuplicate) {
cache[fileName] = keys
keys.forEach((key) => allKeysSet.add(key))
}
})
saveCache(cache)
return [...duplicateKeys]
}
checkForDuplicateKeys
遍历每个文件,检查其键是否在allKeysSet
中存在。- 如果发现重复键,记录到
duplicateKeys
。 - 没有重复键时,将文件的新键更新至
cache
并添加到allKeysSet
。
8. 检查文件并输出结果
const duplicates = checkForDuplicateKeys(files)
if (duplicates.length) {
console.error('Duplicate keys found:', duplicates)
process.exit(1) // 阻止提交
} else {
console.info('🚀 ~ i18n key check complete, no duplicates found')
}
- 检查完毕后,若发现重复键,将输出错误信息并阻止提交(通过
process.exit(1)
退出)。 - 若无重复键,输出成功信息并结束。
代码展示
以下是脚本的代码:
const fs = require('fs')
const path = require('path')
const cacheFilePath = path.join(__dirname, 'temp/i18n_keys_cache.json')
const cacheDirPath = path.join(__dirname, '../src/i18n/zh') // 指定中文目录, 主要维护多语言基础版本
// 检查并创建缓存目录(如果不存在)
const cacheDir = path.dirname(cacheFilePath)
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true })
}
// 获取传入的文件路径列表
const files = process.argv.slice(2)
console.info('🚀 ~ Starting file check:', files)
// 加载缓存并将所有 key 存入 Set 以提高查询效率
function loadCache() {
if (fs.existsSync(cacheFilePath)) {
const cacheContent = fs.readFileSync(cacheFilePath, 'utf8')
const cache = JSON.parse(cacheContent)
return {
cache,
allKeysSet: new Set(Object.values(cache).flat()), // 用 Set 存储所有 key
}
}
// 初始化缓存文件
const cache = initializeCache()
saveCache(cache)
return { cache, allKeysSet: new Set(Object.values(cache).flat()) }
}
// 通过加载维护目录中的所有 JSON 文件来初始化缓存
function initializeCache() {
const cache = {}
const files = fs
.readdirSync(cacheDirPath)
.filter((file) => file.endsWith('.json'))
files.forEach((file) => {
const fileName = path.basename(file, '.json')
const filePath = path.join(cacheDirPath, file)
const content = fs.readFileSync(filePath, 'utf8')
const jsonData = JSON.parse(content)
// 将文件的最后一级键添加到缓存中
cache[fileName] = getLastLevelKeys(jsonData)
})
console.info('🚀 ~ Initialized cache from zh directory with files:', files)
return cache
}
// 保存共享缓存
function saveCache(cache) {
fs.writeFileSync(cacheFilePath, JSON.stringify(cache, null, 2), 'utf8')
}
// 获取 JSON 对象的最后一级 key
function getLastLevelKeys(obj) {
const keys = []
function recurse(o) {
if (typeof o !== 'object' || o === null) return
for (const key in o) {
if (typeof o[key] === 'object') {
recurse(o[key])
} else {
keys.push(key)
}
}
}
recurse(obj)
return keys
}
// 检查重复 key 并更新缓存
function checkForDuplicateKeys(files) {
const { cache, allKeysSet } = loadCache()
const duplicateKeys = new Set()
files.forEach((file) => {
const fileName = path.basename(file, '.json') // 使用文件名作为键
const filePath = path.resolve(file)
const content = fs.readFileSync(filePath, 'utf8')
const jsonData = JSON.parse(content)
const keys = getLastLevelKeys(jsonData)
// 从缓存中删除旧的 key
if (cache[fileName]) {
cache[fileName].forEach((key) => allKeysSet.delete(key)) // 从 Set 中删除
delete cache[fileName]
}
// 检查重复 key,并记录
let hasDuplicate = false
keys.forEach((key) => {
if (allKeysSet.has(key)) {
duplicateKeys.add(key)
hasDuplicate = true
}
})
// 更新缓存:仅在没有重复时保存新 key
if (!hasDuplicate) {
cache[fileName] = keys
keys.forEach((key) => allKeysSet.add(key)) // 添加新 key 到 Set
}
})
saveCache(cache) // 保存更新后的共享缓存
return [...duplicateKeys]
}
// 检查文件
const duplicates = checkForDuplicateKeys(files)
if (duplicates.length) {
console.error('Duplicate keys found:', duplicates)
process.exit(1) // 退出并阻止提交
} else {
console.info('🚀 ~ i18n key check complete, no duplicates found')
}
Loading...