vite-plugin-i18n-validator
Version:
A Vite plugin validates Json files with internationalization support in worker thread.
216 lines (215 loc) • 7.17 kB
JavaScript
// src/index.ts
import { createFilter } from "vite";
import fs from "node:fs";
import pc from "picocolors";
import { Worker } from "worker_threads";
import path from "node:path";
import { fileURLToPath } from "node:url";
async function Plugin(options) {
let checkedFiles = [];
let worker = null;
let textlintWorker = null;
let logger = null;
const finalOptions = Array.isArray(options) ? options : [options];
const traverse = (json, parentKey) => {
const keys = Object.keys(json);
const arr = [];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = json[key];
if (typeof value === "object") {
if (parentKey) {
arr.push(...traverse(value, `${parentKey}.${key}`));
} else {
arr.push(...traverse(value, key));
}
} else {
if (parentKey) {
arr.push(`${parentKey}.${key}`);
} else {
arr.push(`${key}`);
}
}
}
return arr;
};
for (let i = 0; i < finalOptions.length; i++) {
const finalOption = finalOptions[i];
finalOption.enabledBuild = finalOption.enabledBuild || false;
finalOption.filter = createFilter(finalOption.include, finalOption.exclude);
if (!finalOption.baseLocaleFilePath) {
throw new Error("baseLocaleFilePath is required.");
}
try {
const fileText = fs.readFileSync(finalOption.baseLocaleFilePath, "utf-8");
const json = JSON.parse(fileText);
finalOption.cachedBaseFile = traverse(json, "");
} catch (error) {
console.log("error :>> ", error);
throw new Error("baseLocaleFilePath is invalid.");
}
}
return {
name: "vite-plugin-i18n-validator",
enforce: "pre",
configResolved(config) {
for (let i = 0; i < finalOptions.length; i++) {
const finalOption = finalOptions[i];
finalOption.baseLocaleFilePath = path.isAbsolute(
finalOption.baseLocaleFilePath
) ? finalOption.baseLocaleFilePath : path.resolve(config.root, finalOption.baseLocaleFilePath);
}
const __filename2 = fileURLToPath(import.meta.url);
const __dirname2 = path.dirname(__filename2);
logger = config.logger;
worker = new Worker(`${path.resolve(__dirname2, "worker.js")}`);
worker.on("message", ({ errors, file }) => {
if (errors.length > 0) {
const relativePath = path.relative(config.root, file);
for (let i = 0; i < errors.length; i++) {
logger?.info(`${pc.yellow(`${relativePath}`)}: ${errors[i]}`, {
timestamp: true
});
}
}
});
for (let i = 0; i < finalOptions.length; i++) {
const finalOption = finalOptions[i];
if (finalOption.textlint) {
const textlintConfigFilepath = path.resolve(
config.root,
".textlintrc"
);
const baseConfigFilePath = path.resolve(
__dirname2,
".base_textlintrc"
);
const nodeModulesDir = path.resolve(config.root, "node_modules");
if (finalOption.textlint === true) {
finalOption.textlintOption = {
baseConfigFilePath,
createLinterOptions: {},
loadTextlintrcOptions: {
configFilePath: textlintConfigFilepath,
node_modulesDir: nodeModulesDir
}
};
} else if (!finalOption.textlint.loadTextlintrcOptions.configFilePath) {
finalOption.textlint.loadTextlintrcOptions.configFilePath = textlintConfigFilepath;
finalOption.textlint.loadTextlintrcOptions.node_modulesDir = nodeModulesDir;
finalOption.textlint.baseConfigFilePath = baseConfigFilePath;
finalOption.textlintOption = {
...finalOption.textlint
};
}
}
textlintWorker = new Worker(
`${path.resolve(__dirname2, "textlintWorker.js")}`
);
textlintWorker.on("message", (msg) => {
if (msg.results.length === 0) {
return;
}
for (let i2 = 0; i2 < msg.results.length; i2++) {
const result = msg.results[i2];
if (result.messages.length === 0) {
continue;
}
const relativePath = path.relative(config.root, result.filePath);
for (let j = 0; j < result.messages.length; j++) {
const message = result.messages[j];
logger?.info(
`${pc.yellow(`${relativePath}`)}:${message.loc.start.line}:${message.loc.start.column}: ${message.message}`,
{ timestamp: true }
);
}
}
});
return;
}
},
async handleHotUpdate(context) {
let json = null;
for (let i = 0; i < finalOptions.length; i++) {
const finalOption = finalOptions[i];
if (!finalOption.cachedBaseFile) {
return;
}
if (!finalOption.filter(context.file) && context.file !== finalOption.baseLocaleFilePath) {
continue;
}
if (!json) {
const text = await context.read();
json = JSON.parse(text);
}
if (context.file === finalOption.baseLocaleFilePath) {
finalOption.cachedBaseFile = traverse(json);
}
try {
worker?.postMessage({
json,
cachedBaseFile: finalOption.cachedBaseFile,
prohibitedValues: finalOption.prohibitedValues,
prohibitedKey: finalOption.prohibitedKeys,
file: context.file,
ignoreKeys: finalOption.ignoreKeys
});
if (!finalOption.textlint) {
return;
}
textlintWorker?.postMessage({
textlintOption: finalOption.textlintOption,
file: context.file
});
} catch (error) {
console.log("error :>> ", error);
}
}
},
buildStart() {
checkedFiles = [];
},
buildEnd() {
worker?.terminate();
textlintWorker?.terminate();
},
transform(_code, id) {
if (checkedFiles.includes(id)) {
return;
}
checkedFiles.push(id);
for (let i = 0; i < finalOptions.length; i++) {
const finalOption = finalOptions[i];
if (finalOption.enabledBuild === false) {
continue;
}
if (!finalOption.cachedBaseFile) {
continue;
}
if (!finalOption.filter(id)) {
continue;
}
const json = JSON.parse(_code);
worker?.postMessage({
json,
cachedBaseFile: finalOption.cachedBaseFile,
prohibitedValues: finalOption.prohibitedValues,
prohibitedKey: finalOption.prohibitedKeys,
file: id,
ignoreKeys: finalOption.ignoreKeys
});
if (!finalOption.textlint) {
return;
}
textlintWorker?.postMessage({
textlintOption: finalOption.textlintOption,
file: id
});
}
checkedFiles.push(id);
}
};
}
export {
Plugin as default
};