chore: 整理 third-party Dark Reader 目录并移除本地说明文件跟踪

This commit is contained in:
2026-08-08 17:38:08 +08:00
parent 202ab537d1
commit 0bb552b026
1004 changed files with 115066 additions and 20081 deletions
+228
View File
@@ -0,0 +1,228 @@
// @ts-check
import fs from 'node:fs/promises';
import {readFile, writeFile, fileExists, httpsRequest, timeout, log} from './utils.js';
// To use this tool:
// 1. Edit a line in en.config.
// 2. Run `npm run translate-en-message message_id`.
// 3. The line will be translated and written into other locales.
// TODO: If necessary, new @id and empty lines should be copied as well.
// TODO: Serbian translates into Cyrillic, but it is somehow possible to do Latin.
/** @typedef {{locale: string; file: string; messages: Map<string, string>}} LocaleFile */
const LOCALES_ROOT = 'src/_locales';
/**
* Translates `en` locale message for all locales
* @param {string} messageId Message ID
*/
async function translateEnMessage(messageId) {
log(`Translating message ${messageId}`);
const supportedLocales = await getSupportedLocales();
const enFiles = await getLocaleFiles('en');
let found = false;
for (const enFile of enFiles) {
const enContent = await readFile(enFile);
const enMessages = parseLocale(enContent);
if (enMessages.has(messageId)) {
found = true;
const enMessage = /** @type {string} */(enMessages.get(messageId));
for (const locale of supportedLocales) {
if (locale === 'en') {
continue;
}
await timeout(1000);
const locFile = `${enFile.slice(0, enFile.lastIndexOf('en.config'))}${locale}.config`;
const locContent = await readFile(locFile);
const locMessages = parseLocale(locContent);
const translated = await translate(enMessage, locale);
locMessages.set(messageId, translated);
const output = stringifyLocale(locMessages);
await writeFile(locFile, output);
log(`${locale}: ${translated}`);
}
}
}
if (!found) {
throw new Error(`Could not find message ${messageId}.`);
}
log.ok('Translation done');
}
/**
* Translates new `en` locale lines for all locales
*/
async function translateNewEnMessages() {
log('Translating new lines');
const supportedLocales = await getSupportedLocales();
const enFiles = await getLocaleFiles('en');
for (const enFile of enFiles) {
const enContent = await readFile(enFile);
const enMessages = parseLocale(enContent);
for (const locale of supportedLocales) {
if (locale === 'en') {
continue;
}
/** @type {Map<string, string>} */
let locMessages = new Map();
const locFile = `${enFile.slice(0, enFile.lastIndexOf('en.config'))}${locale}.config`;
if (await fileExists(locFile)) {
const locContent = await readFile(locFile);
locMessages = parseLocale(locContent);
}
for (const messageId of enMessages.keys()) {
const enMessage = /** @type {string} */(enMessages.get(messageId));
const translated = await translate(enMessage, locale);
locMessages.set(messageId, translated);
log(`${locale}: ${translated}`);
}
const output = stringifyLocale(locMessages);
await writeFile(locFile, output);
}
}
log.ok('Translation done');
}
/**
* @param {string} content
* @returns {Map<string, string>}
*/
function parseLocale(content) {
/** @type {Map<string, string>} */
const messages = new Map();
const lines = content.split('\n');
let id = '';
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('@')) {
id = line.substring(1);
} else if (line.startsWith('#')) {
// Ignore
} else if (messages.has(id)) {
const message = messages.get(id);
messages.set(id, `${message}\n${line}`);
} else {
messages.set(id, line);
}
}
messages.forEach((value, id) => {
messages.set(id, value.trim());
});
return messages;
}
/**
* @param {Map<string, string>} messages
* @returns {string}
*/
function stringifyLocale(messages) {
/** @type {string[]} */
const lines = [];
messages.forEach((message, id) => {
lines.push(`@${id}`);
const hasDoubleNewLines = /\n\n/.test(message);
message.split('\n')
.filter((line) => line.trim())
.forEach((line, index, filtered) => {
lines.push(line);
if (hasDoubleNewLines && index < filtered.length - 1) {
lines.push('');
}
});
lines.push('');
});
return lines.join('\n');
}
/**
* @returns {Promise<string[]>}
*/
async function getSupportedLocales() {
const fileList = await fs.readdir(LOCALES_ROOT);
/** @type {string[]} */
const locales = [];
for (const file of fileList) {
if (file.endsWith('.config')) {
const locale = file.substring(0, file.lastIndexOf('.config'));
locales.push(locale);
}
}
return locales;
}
/**
* @returns {Promise<string[]>}
*/
async function getLocaleFiles(locale) {
/** @type {string[]} */
const results = [];
/** @type {(dir: string) => Promise<void>} */
const walk = async (dir) => {
const entries = await fs.readdir(dir);
const matched = entries.filter((f) => f === `${locale}.config` || f.endsWith(`.${locale}.config`));
results.push(...matched);
for (const e of entries) {
const p = `${dir}/${e}`;
const stat = await fs.stat(p);
if (stat.isDirectory()) {
walk(p);
}
}
};
await walk(LOCALES_ROOT);
return results;
}
/**
* @param {string} text
* @param {string} lang
* @return {Promise<string>}
*/
async function translate(text, lang) {
const url = new URL('https://translate.googleapis.com/translate_a/single');
url.search = (new URLSearchParams({
client: 'gtx',
sl: 'en-US',
tl: lang,
dt: 't',
dj: '1',
q: text,
})).toString();
const response = await httpsRequest(url.toString());
const translation = JSON.parse(response.text());
return translation.sentences.map((s) => s.trans).join('\n').replaceAll(/\n+/g, '\n');
}
const args = process.argv.slice(2);
if (args[0] === '--message') {
const messageId = args[1];
translateEnMessage(messageId);
}
if (args.includes('--new-messages')) {
translateNewEnMessages();
}