dokploy/extract.js
2025-04-06 12:34:47 +08:00

271 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

console.log('Creating translation extractor script...');
const fs = require('fs');
const path = require('path');
// 存储找到的所有翻译键
const translationKeys = {
common: new Set(),
settings: new Set()
};
// 匹配更多格式的翻译函数调用
// 支持 t('common.xxx')、t("common.xxx")、t(`common.xxx`)
const translationPatterns = [
/t\(\s*['"]([a-zA-Z0-9._-]+)['"]?\s*[,)]/g, // t('key') 或 t("key")
/t\(\s*`([a-zA-Z0-9._-]+)`\s*[,)]/g, // t(`key`)
/useTranslation\(\s*["']([a-zA-Z0-9._-]+)["']\s*\)/g, // useTranslation('namespace')
/serverSideTranslations\([^)]*["']([a-zA-Z0-9._-]+)["']/g // serverSideTranslations(..., ['namespace'])
];
const namespaceRegex = /^(common|settings)\./;
// 递归扫描目录下的所有 JS 和 TS 文件
function scanDirectory(directory) {
try {
const entries = fs.readdirSync(directory, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory() && !fullPath.includes('node_modules') && !fullPath.includes('.next')) {
scanDirectory(fullPath);
} else if (entry.isFile() && /\.(js|jsx|ts|tsx)$/.test(entry.name)) {
try {
const content = fs.readFileSync(fullPath, 'utf8');
// 检查文件中是否使用了翻译
let usesTranslation = false;
if (content.includes('useTranslation') || content.includes('t(') || content.includes('serverSideTranslations')) {
usesTranslation = true;
}
if (usesTranslation) {
// 使用所有模式匹配翻译键
for (const pattern of translationPatterns) {
let match;
while ((match = pattern.exec(content)) !== null) {
const key = match[1];
// 检查是否有命名空间
const namespaceMatch = key.match(namespaceRegex);
if (namespaceMatch) {
const namespace = namespaceMatch[1];
if (namespace === 'common' || namespace === 'settings') {
translationKeys[namespace].add(key);
}
}
// 如果文件中导入了特定命名空间,所有的 t(key) 都属于该命名空间
if (content.includes(`useTranslation('common')`) || content.includes(`useTranslation("common")`)) {
if (!key.includes('.')) {
translationKeys.common.add(`common.${key}`);
}
}
if (content.includes(`useTranslation('settings')`) || content.includes(`useTranslation("settings")`)) {
if (!key.includes('.')) {
translationKeys.settings.add(`settings.${key}`);
}
}
}
}
// 控制台输出被处理的文件及其找到的翻译键
if (usesTranslation) {
console.log(`检查文件: ${fullPath}`);
}
}
} catch (error) {
console.error(`Error reading file ${fullPath}:`, error);
}
}
}
} catch (error) {
console.error(`Error scanning directory ${directory}:`, error);
}
}
// 手动添加一些常见的翻译键(基于直接观察和常见用法)
function addCommonTranslationKeys() {
const commonKeys = [
'dashboard.title', 'dashboard.overview', 'dashboard.projects', 'dashboard.servers',
'dashboard.docker', 'dashboard.monitoring', 'dashboard.settings', 'dashboard.logout',
'dashboard.profile', 'dashboard.terminal', 'dashboard.containers', 'dashboard.images',
'dashboard.volumes', 'dashboard.networks',
'button.create', 'button.edit', 'button.delete', 'button.cancel',
'button.save', 'button.confirm', 'button.back', 'button.next', 'button.finish',
'status.running', 'status.stopped', 'status.error', 'status.pending',
'status.success', 'status.failed',
'form.required', 'form.invalid', 'form.submit', 'form.reset',
'notification.success', 'notification.error', 'notification.warning', 'notification.info',
'time.now', 'time.minutes', 'time.hours', 'time.days',
'filter.all', 'filter.active', 'filter.inactive',
'sort.asc', 'sort.desc',
'search.placeholder', 'search.noResults',
'pagination.prev', 'pagination.next', 'pagination.of',
'error.notFound', 'error.serverError', 'error.unauthorized', 'error.forbidden',
'loading', 'empty', 'more', 'less',
'project.create', 'project.edit', 'project.delete', 'project.name', 'project.description',
'service.create', 'service.edit', 'service.delete', 'service.name', 'service.type',
'domain.add', 'domain.remove',
'environment.variables', 'environment.add', 'environment.edit',
'environment.name', 'environment.value'
];
commonKeys.forEach(key => {
translationKeys.common.add(`common.${key}`);
});
}
// 读取现有翻译文件
function readTranslationFile(filePath) {
try {
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf8');
return JSON.parse(content);
}
} catch (error) {
console.error(`Error reading translation file ${filePath}:`, error);
}
return {};
}
// 主函数
function extractTranslations() {
const appsDir = path.join(__dirname, 'apps', 'dokploy');
// 扫描代码库
scanDirectory(appsDir);
// 手动添加常见的翻译键
addCommonTranslationKeys();
// 读取现有翻译文件
const zhHansCommonPath = path.join(appsDir, 'public', 'locales', 'zh-Hans', 'common.json');
const zhHansSettingsPath = path.join(appsDir, 'public', 'locales', 'zh-Hans', 'settings.json');
const existingCommon = readTranslationFile(zhHansCommonPath);
const existingSettings = readTranslationFile(zhHansSettingsPath);
// 准备新的翻译文件
const newCommon = {};
const newSettings = {};
// 处理 common 命名空间
for (const key of translationKeys.common) {
const shortKey = key.replace('common.', '');
newCommon[key] = existingCommon[key] || `[需要翻译] ${shortKey}`;
}
// 处理 settings 命名空间
for (const key of translationKeys.settings) {
const shortKey = key.replace('settings.', '');
newSettings[key] = existingSettings[key] || `[需要翻译] ${shortKey}`;
}
// 输出结果
console.log('=== 提取的 common 翻译键 ===');
console.log(Array.from(translationKeys.common).sort().join('\n'));
console.log(`\n共找到 ${translationKeys.common.size} 个 common 翻译键`);
console.log('\n=== 提取的 settings 翻译键 ===');
console.log(Array.from(translationKeys.settings).sort().join('\n'));
console.log(`\n共找到 ${translationKeys.settings.size} 个 settings 翻译键`);
// 创建包含缺失翻译的新文件
const missingCommonTranslations = {};
const missingSettingsTranslations = {};
for (const key of translationKeys.common) {
if (!existingCommon[key]) {
const shortKey = key.replace('common.', '');
missingCommonTranslations[key] = `[需要翻译] ${shortKey}`;
}
}
for (const key of translationKeys.settings) {
if (!existingSettings[key]) {
const shortKey = key.replace('settings.', '');
missingSettingsTranslations[key] = `[需要翻译] ${shortKey}`;
}
}
// 输出缺失的翻译
console.log('\n=== 缺失的 common 翻译 ===');
console.log(JSON.stringify(missingCommonTranslations, null, 2));
console.log(`\n共缺失 ${Object.keys(missingCommonTranslations).length} 个 common 翻译`);
console.log('\n=== 缺失的 settings 翻译 ===');
console.log(JSON.stringify(missingSettingsTranslations, null, 2));
console.log(`\n共缺失 ${Object.keys(missingSettingsTranslations).length} 个 settings 翻译`);
// 输出可以直接复制到文件中的完整翻译对象
console.log('\n=== 完整的 common.json 内容 ===');
const fullCommon = { ...existingCommon };
translationKeys.common.forEach(key => {
if (!fullCommon[key]) {
const shortKey = key.replace('common.', '');
fullCommon[key] = `[翻译] ${shortKey}`;
}
});
console.log(JSON.stringify(fullCommon, null, 2));
console.log('\n=== 完整的 settings.json 内容 ===');
const fullSettings = { ...existingSettings };
translationKeys.settings.forEach(key => {
if (!fullSettings[key]) {
const shortKey = key.replace('settings.', '');
fullSettings[key] = `[翻译] ${shortKey}`;
}
});
console.log(JSON.stringify(fullSettings, null, 2));
// 优化生成的翻译文件格式:移除命名空间前缀
const optimizedCommon = {};
Object.keys(fullCommon).forEach(key => {
const shortKey = key.replace('common.', '');
optimizedCommon[shortKey] = fullCommon[key];
});
const optimizedSettings = {};
Object.keys(fullSettings).forEach(key => {
const shortKey = key.replace('settings.', '');
optimizedSettings[shortKey] = fullSettings[key];
});
// 写入文件
fs.writeFileSync('missing-common-translations.json', JSON.stringify(missingCommonTranslations, null, 2), 'utf8');
fs.writeFileSync('missing-settings-translations.json', JSON.stringify(missingSettingsTranslations, null, 2), 'utf8');
fs.writeFileSync('full-common-translations.json', JSON.stringify(fullCommon, null, 2), 'utf8');
fs.writeFileSync('full-settings-translations.json', JSON.stringify(fullSettings, null, 2), 'utf8');
fs.writeFileSync('optimized-common-translations.json', JSON.stringify(optimizedCommon, null, 2), 'utf8');
fs.writeFileSync('optimized-settings-translations.json', JSON.stringify(optimizedSettings, null, 2), 'utf8');
console.log('\n翻译提取完成');
console.log('文件已保存:');
console.log('- missing-common-translations.json: 缺失的 common 翻译');
console.log('- missing-settings-translations.json: 缺失的 settings 翻译');
console.log('- full-common-translations.json: 完整的 common 翻译(包含命名空间)');
console.log('- full-settings-translations.json: 完整的 settings 翻译(包含命名空间)');
console.log('- optimized-common-translations.json: 优化格式的 common 翻译(不含命名空间)');
console.log('- optimized-settings-translations.json: 优化格式的 settings 翻译(不含命名空间)');
}
extractTranslations();