chore: format all common files (#431)

This commit is contained in:
tecvan
2025-07-31 21:46:47 +08:00
committed by GitHub
parent 8136ddd82d
commit 40088b0a05
60 changed files with 658 additions and 288 deletions

View File

@@ -1,5 +1,6 @@
{
"dependencies": {
"@eslint/compat": "^1.2.2"
}
"dependencies": {
"@eslint/compat": "^1.2.2"
}
}

View File

@@ -21,3 +21,4 @@
"@types/node": "^18.6.3"
}
}

View File

@@ -17,3 +17,4 @@
"typescript": "^5.0.0"
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command } from 'commander';
import { CliOptions } from '../types/config';
@@ -14,7 +30,11 @@ export const createProgram = (): Command => {
program
.requiredOption('-r, --root <directory>', '需要处理的根目录')
.option('-e, --exts <extensions>', '文件扩展名,用逗号分隔 (例: ts,js,go)', '')
.option(
'-e, --exts <extensions>',
'文件扩展名,用逗号分隔 (例: ts,js,go)',
'',
)
.option('--access-key-id <key>', '火山引擎 Access Key ID')
.option('--secret-access-key <key>', '火山引擎 Secret Access Key')
.option('--region <region>', '火山引擎服务区域', 'cn-beijing')
@@ -48,7 +68,7 @@ export const parseOptions = (program: Command): CliOptions => {
dryRun: options.dryRun,
verbose: options.verbose,
output: options.output,
config: options.config
config: options.config,
};
};

View File

@@ -1,4 +1,25 @@
import { AppConfig, CliOptions, TranslationConfig, ProcessingConfig } from '../types/config';
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
AppConfig,
CliOptions,
TranslationConfig,
ProcessingConfig,
} from '../types/config';
import { deepMerge } from '../utils/fp';
/**
@@ -13,28 +34,63 @@ const DEFAULT_CONFIG: AppConfig = {
targetLanguage: 'en',
maxRetries: 3,
timeout: 30000,
concurrency: 3
concurrency: 3,
},
processing: {
defaultExtensions: [
'ts', 'tsx', 'js', 'jsx', 'go', 'md', 'txt', 'json',
'yaml', 'yml', 'toml', 'ini', 'conf', 'config',
'sh', 'bash', 'zsh', 'fish', 'py', 'css', 'scss', 'sass', 'less',
'html', 'htm', 'xml', 'php', 'rb', 'rs', 'java', 'c', 'h',
'cpp', 'cxx', 'cc', 'hpp', 'cs', 'thrift'
'ts',
'tsx',
'js',
'jsx',
'go',
'md',
'txt',
'json',
'yaml',
'yml',
'toml',
'ini',
'conf',
'config',
'sh',
'bash',
'zsh',
'fish',
'py',
'css',
'scss',
'sass',
'less',
'html',
'htm',
'xml',
'php',
'rb',
'rs',
'java',
'c',
'h',
'cpp',
'cxx',
'cc',
'hpp',
'cs',
'thrift',
],
outputFormat: 'console'
outputFormat: 'console',
},
git: {
ignorePatterns: ['node_modules/**', '.git/**', 'dist/**', 'build/**'],
includeUntracked: false
}
includeUntracked: false,
},
};
/**
* Load configuration from file
*/
export const loadConfigFromFile = async (configPath: string): Promise<Partial<AppConfig>> => {
export const loadConfigFromFile = async (
configPath: string,
): Promise<Partial<AppConfig>> => {
try {
const fs = await import('fs/promises');
const configContent = await fs.readFile(configPath, 'utf-8');
@@ -48,11 +104,19 @@ export const loadConfigFromFile = async (configPath: string): Promise<Partial<Ap
/**
* Create configuration from command line options
*/
export const createConfigFromOptions = (options: CliOptions): Partial<AppConfig> => {
export const createConfigFromOptions = (
options: CliOptions,
): Partial<AppConfig> => {
const config: Partial<AppConfig> = {};
// translation configuration
if (options.accessKeyId || options.secretAccessKey || options.region || options.sourceLanguage || options.targetLanguage) {
if (
options.accessKeyId ||
options.secretAccessKey ||
options.region ||
options.sourceLanguage ||
options.targetLanguage
) {
config.translation = {} as Partial<TranslationConfig>;
if (options.accessKeyId) {
config.translation!.accessKeyId = options.accessKeyId;
@@ -90,10 +154,9 @@ export const createConfigFromOptions = (options: CliOptions): Partial<AppConfig>
* merge configuration
*/
export const mergeConfigs = (...configs: Partial<AppConfig>[]): AppConfig => {
return configs.reduce(
(merged, config) => deepMerge(merged, config),
{ ...DEFAULT_CONFIG }
) as AppConfig;
return configs.reduce((merged, config) => deepMerge(merged, config), {
...DEFAULT_CONFIG,
}) as AppConfig;
};
/**
@@ -118,41 +181,65 @@ export const loadConfig = async (options: CliOptions): Promise<AppConfig> => {
/**
* verify configuration
*/
export const validateConfig = (config: AppConfig): { valid: boolean; errors: string[] } => {
export const validateConfig = (
config: AppConfig,
): { valid: boolean; errors: string[] } => {
const errors: string[] = [];
// Verify Volcano Engine Access Key ID
if (!config.translation.accessKeyId) {
errors.push('火山引擎 Access Key ID 未设置请通过环境变量VOLC_ACCESS_KEY_ID或--access-key-id参数提供');
errors.push(
'火山引擎 Access Key ID 未设置请通过环境变量VOLC_ACCESS_KEY_ID或--access-key-id参数提供',
);
}
// Verify Volcano Engine Secret Access Key
if (!config.translation.secretAccessKey) {
errors.push('火山引擎 Secret Access Key 未设置请通过环境变量VOLC_SECRET_ACCESS_KEY或--secret-access-key参数提供');
errors.push(
'火山引擎 Secret Access Key 未设置请通过环境变量VOLC_SECRET_ACCESS_KEY或--secret-access-key参数提供',
);
}
// validation area
const validRegions = ['cn-beijing', 'ap-southeast-1', 'us-east-1'];
if (!validRegions.includes(config.translation.region)) {
console.warn(`未知的区域: ${config.translation.region},建议使用: ${validRegions.join(', ')}`);
console.warn(
`未知的区域: ${config.translation.region},建议使用: ${validRegions.join(
', ',
)}`,
);
}
// Verify language code
const validLanguages = ['zh', 'en', 'ja', 'ko', 'fr', 'de', 'es', 'pt', 'ru'];
if (!validLanguages.includes(config.translation.sourceLanguage)) {
console.warn(`未知的源语言: ${config.translation.sourceLanguage},建议使用: ${validLanguages.join(', ')}`);
console.warn(
`未知的源语言: ${
config.translation.sourceLanguage
},建议使用: ${validLanguages.join(', ')}`,
);
}
if (!validLanguages.includes(config.translation.targetLanguage)) {
console.warn(`未知的目标语言: ${config.translation.targetLanguage},建议使用: ${validLanguages.join(', ')}`);
console.warn(
`未知的目标语言: ${
config.translation.targetLanguage
},建议使用: ${validLanguages.join(', ')}`,
);
}
// validation concurrency
if (config.translation.concurrency < 1 || config.translation.concurrency > 10) {
if (
config.translation.concurrency < 1 ||
config.translation.concurrency > 10
) {
errors.push('并发数应该在1-10之间');
}
// verification timeout
if (config.translation.timeout < 1000 || config.translation.timeout > 300000) {
if (
config.translation.timeout < 1000 ||
config.translation.timeout > 300000
) {
errors.push('超时时间应该在1000-300000毫秒之间');
}
@@ -162,7 +249,10 @@ export const validateConfig = (config: AppConfig): { valid: boolean; errors: str
/**
* Print configuration information
*/
export const printConfigInfo = (config: AppConfig, verbose: boolean = false): void => {
export const printConfigInfo = (
config: AppConfig,
verbose: boolean = false,
): void => {
console.log('🔧 当前配置:');
console.log(` 区域: ${config.translation.region}`);
console.log(` 源语言: ${config.translation.sourceLanguage}`);
@@ -172,11 +262,23 @@ export const printConfigInfo = (config: AppConfig, verbose: boolean = false): vo
console.log(` 输出格式: ${config.processing.outputFormat}`);
if (verbose) {
console.log(` Access Key ID: ${config.translation.accessKeyId ? '已设置' : '未设置'}`);
console.log(` Secret Access Key: ${config.translation.secretAccessKey ? '已设置' : '未设置'}`);
console.log(
` Access Key ID: ${
config.translation.accessKeyId ? '已设置' : '未设置'
}`,
);
console.log(
` Secret Access Key: ${
config.translation.secretAccessKey ? '已设置' : '未设置'
}`,
);
console.log(` 超时时间: ${config.translation.timeout}ms`);
console.log(` 默认扩展名: ${config.processing.defaultExtensions.join(', ')}`);
console.log(
` 默认扩展名: ${config.processing.defaultExtensions.join(', ')}`,
);
console.log(` 忽略模式: ${config.git.ignorePatterns.join(', ')}`);
console.log(` 包含未跟踪文件: ${config.git.includeUntracked ? '是' : '否'}`);
console.log(
` 包含未跟踪文件: ${config.git.includeUntracked ? '是' : '否'}`,
);
}
};

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#!/usr/bin/env node
import { createProgram, parseOptions, showHelp } from './cli/command';
@@ -102,7 +118,9 @@ async function processRepository(
console.log(`\n📝 ${file.path}:`);
translations.forEach((translation, index) => {
console.log(
` ${index + 1}. "${translation.original}" → "${translation.translated}"`,
` ${index + 1}. "${translation.original}" → "${
translation.translated
}"`,
);
});
}
@@ -116,10 +134,7 @@ async function processRepository(
);
const operation = { file: file.path, replacements };
const result = await replaceCommentsInFile(
file,
operation,
);
const result = await replaceCommentsInFile(file, operation);
if (!result.success) {
throw new Error(result.error || '文件替换失败');

View File

@@ -1,10 +1,26 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
SourceFile,
ChineseComment,
ParsedComment,
FileWithComments,
CommentType,
MultiLineContext
MultiLineContext,
} from '../types/index';
import { getCommentPatterns } from '../utils/language';
import { containsChinese, cleanCommentText } from '../utils/chinese';
@@ -69,15 +85,17 @@ const parseSingleLineComments = (
let matchCount = 0;
const maxMatches = 100; // Limit each line to a maximum of 100 matches
let lastIndex = 0;
while ((match = pattern.exec(line)) !== null) {
// Multiple protections against infinite loops
matchCount++;
if (matchCount > maxMatches) {
console.warn(`⚠️ 单行匹配次数过多,中断处理: ${line.substring(0, 50)}...`);
console.warn(
`⚠️ 单行匹配次数过多,中断处理: ${line.substring(0, 50)}...`,
);
break;
}
// Check if lastIndex is advancing to prevent an infinite loop
if (pattern.global) {
if (pattern.lastIndex <= lastIndex) {
@@ -89,7 +107,7 @@ const parseSingleLineComments = (
}
lastIndex = pattern.lastIndex;
}
if (match[1]) {
const commentContent = match[1];
let commentStartIndex = match.index!;
@@ -246,7 +264,6 @@ const parseMultiLineComments = (
const commentContent = line.substring(0, endMatch.index!);
commentLines.push(commentContent);
comments.push({
content: commentLines.join('\n'),
startLine: commentStart!.line,
@@ -298,11 +315,14 @@ export const filterChineseComments = (
language?: string,
): ChineseComment[] => {
const result: ChineseComment[] = [];
for (const comment of comments) {
if (comment.type === 'multi-line' && comment.content.includes('\n')) {
// Multi-line comments: line-by-line processing
const multiLineResults = processMultiLineCommentForChinese(comment, language);
const multiLineResults = processMultiLineCommentForChinese(
comment,
language,
);
result.push(...multiLineResults);
} else if (containsChinese(comment.content)) {
// Single-line comments or single-line multi-line comments
@@ -316,7 +336,7 @@ export const filterChineseComments = (
});
}
}
return result;
};
@@ -329,14 +349,14 @@ const processMultiLineCommentForChinese = (
): ChineseComment[] => {
const lines = comment.content.split('\n');
const result: ChineseComment[] = [];
lines.forEach((line, lineIndex) => {
const cleanedLine = cleanCommentText(line, 'multi-line', language);
if (containsChinese(cleanedLine)) {
// Calculate the position of this line in the original file
const actualLineNumber = comment.startLine + lineIndex;
// Create a comment object representing this line
const lineComment: ChineseComment = {
content: cleanedLine,
@@ -350,14 +370,14 @@ const processMultiLineCommentForChinese = (
isPartOfMultiLine: true,
originalComment: comment,
lineIndexInComment: lineIndex,
totalLinesInComment: lines.length
}
totalLinesInComment: lines.length,
},
};
result.push(lineComment);
}
});
return result;
};
@@ -393,7 +413,9 @@ export const detectChineseInFile = (file: SourceFile): ChineseComment[] => {
/**
* Batch detection of Chinese comments in multiple files
*/
export const detectChineseInFiles = (files: SourceFile[]): FileWithComments[] => {
export const detectChineseInFiles = (
files: SourceFile[],
): FileWithComments[] => {
const results: FileWithComments[] = [];
for (let i = 0; i < files.length; i++) {
@@ -428,7 +450,9 @@ export const detectChineseInFiles = (files: SourceFile[]): FileWithComments[] =>
/**
* Get annotation statistics
*/
export const getCommentStats = (files: SourceFile[]): {
export const getCommentStats = (
files: SourceFile[],
): {
totalFiles: number;
filesWithComments: number;
totalComments: number;
@@ -441,12 +465,15 @@ export const getCommentStats = (files: SourceFile[]): {
const commentsByType: Record<CommentType, number> = {
'single-line': 0,
'multi-line': 0,
'documentation': 0
documentation: 0,
};
files.forEach(file => {
const allComments = parseComments(file);
const chineseCommentsInFile = filterChineseComments(allComments, file.language);
const chineseCommentsInFile = filterChineseComments(
allComments,
file.language,
);
if (chineseCommentsInFile.length > 0) {
filesWithComments++;
@@ -465,6 +492,6 @@ export const getCommentStats = (files: SourceFile[]): {
filesWithComments,
totalComments,
chineseComments,
commentsByType
commentsByType,
};
};

View File

@@ -1,13 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { promises as fs } from 'fs';
import { SourceFile, FileScanConfig, Result } from '../types/index';
import { detectLanguage, filterFilesByExtensions, isTextFile } from '../utils/language';
import {
detectLanguage,
filterFilesByExtensions,
isTextFile,
} from '../utils/language';
import { getGitTrackedFiles, getAllGitFiles } from '../utils/git';
import { tryCatch } from '../utils/fp';
/**
* Read the file contents and create a SourceFile object
*/
export const readSourceFile = async (filePath: string): Promise<Result<SourceFile>> => {
export const readSourceFile = async (
filePath: string,
): Promise<Result<SourceFile>> => {
return tryCatch(async () => {
const content = await fs.readFile(filePath, 'utf-8');
const language = detectLanguage(filePath);
@@ -15,7 +37,7 @@ export const readSourceFile = async (filePath: string): Promise<Result<SourceFil
return {
path: filePath,
content,
language
language,
};
});
};
@@ -23,14 +45,17 @@ export const readSourceFile = async (filePath: string): Promise<Result<SourceFil
/**
* Batch reading of source files
*/
export const readSourceFiles = async (filePaths: string[]): Promise<SourceFile[]> => {
export const readSourceFiles = async (
filePaths: string[],
): Promise<SourceFile[]> => {
const results = await Promise.allSettled(
filePaths.map(path => readSourceFile(path))
filePaths.map(path => readSourceFile(path)),
);
return results
.filter((result): result is PromiseFulfilledResult<Result<SourceFile>> =>
result.status === 'fulfilled' && result.value.success
.filter(
(result): result is PromiseFulfilledResult<Result<SourceFile>> =>
result.status === 'fulfilled' && result.value.success,
)
.map(result => (result.value as { success: true; data: SourceFile }).data);
};
@@ -38,7 +63,9 @@ export const readSourceFiles = async (filePaths: string[]): Promise<SourceFile[]
/**
* Get the source code file in the Git repository
*/
export const getSourceFiles = async (config: FileScanConfig): Promise<Result<string[]>> => {
export const getSourceFiles = async (
config: FileScanConfig,
): Promise<Result<string[]>> => {
const { root, extensions, includeUntracked } = config;
return tryCatch(async () => {
@@ -66,7 +93,9 @@ export const getSourceFiles = async (config: FileScanConfig): Promise<Result<str
/**
* Scan and read all source code files
*/
export const scanSourceFiles = async (config: FileScanConfig): Promise<Result<SourceFile[]>> => {
export const scanSourceFiles = async (
config: FileScanConfig,
): Promise<Result<SourceFile[]>> => {
return tryCatch(async () => {
const filesResult = await getSourceFiles(config);
@@ -94,28 +123,32 @@ export const isFileAccessible = async (filePath: string): Promise<boolean> => {
/**
* Get file statistics
*/
export const getFileStats = async (filePaths: string[]): Promise<{
export const getFileStats = async (
filePaths: string[],
): Promise<{
total: number;
accessible: number;
textFiles: number;
supportedFiles: number;
}> => {
const accessibilityResults = await Promise.allSettled(
filePaths.map(isFileAccessible)
filePaths.map(isFileAccessible),
);
const accessible = accessibilityResults.filter(
(result): result is PromiseFulfilledResult<boolean> =>
result.status === 'fulfilled' && result.value
result.status === 'fulfilled' && result.value,
).length;
const textFiles = filePaths.filter(isTextFile).length;
const supportedFiles = filePaths.filter(path => detectLanguage(path) !== 'other').length;
const supportedFiles = filePaths.filter(
path => detectLanguage(path) !== 'other',
).length;
return {
total: filePaths.length,
accessible,
textFiles,
supportedFiles
supportedFiles,
};
};

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ProcessingReport,
ProcessingStats,
@@ -198,18 +214,22 @@ export const generateMarkdownReport = (report: ProcessingReport): string => {
detail.status === 'success'
? '✅'
: detail.status === 'error'
? '❌'
: detail.status === 'skipped'
? '⏭️'
: '🔄';
? '❌'
: detail.status === 'skipped'
? '⏭️'
: '🔄';
markdown += `| ${detail.file} | ${status} | ${detail.commentCount} | ${duration} | ${detail.errorMessage || '-'} |\n`;
markdown += `| ${detail.file} | ${status} | ${
detail.commentCount
} | ${duration} | ${detail.errorMessage || '-'} |\n`;
});
if (stats.errors.length > 0) {
markdown += '\n## ❌ 错误详情\n\n';
stats.errors.forEach((error, index) => {
markdown += `${index + 1}. **${error.file}**\n \`\`\`\n ${error.error}\n \`\`\`\n\n`;
markdown += `${index + 1}. **${error.file}**\n \`\`\`\n ${
error.error
}\n \`\`\`\n\n`;
});
}
@@ -276,7 +296,9 @@ export class ProgressDisplay {
const speed = current / elapsed;
const eta = speed > 0 ? (this.total - current) / speed : 0;
let line = `进度: ${current}/${this.total} (${percentage}%) | 耗时: ${elapsed.toFixed(1)}s`;
let line = `进度: ${current}/${
this.total
} (${percentage}%) | 耗时: ${elapsed.toFixed(1)}s`;
if (eta > 0) {
line += ` | 预计剩余: ${eta.toFixed(1)}s`;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
TranslationResult,
TranslationContext,
@@ -7,7 +23,10 @@ import {
import { TranslationConfig } from '../types/config';
import { retry, chunk } from '../utils/fp';
import { isValidTranslation } from '../utils/chinese';
import { translate as volcTranslate, TranslateConfig as VolcTranslateConfig } from '../volc/translate';
import {
translate as volcTranslate,
TranslateConfig as VolcTranslateConfig,
} from '../volc/translate';
/**
* Translation services
@@ -59,7 +78,7 @@ export class TranslationService {
private async callVolcTranslate(texts: string[]): Promise<string[]> {
const volcConfig = this.toVolcConfig();
const response = await volcTranslate(texts, volcConfig);
return response.TranslationList.map(item => item.Translation);
}
@@ -101,7 +120,9 @@ export class TranslationService {
return result;
} catch (error) {
throw new TranslationError(
`Translation failed: ${error instanceof Error ? error.message : String(error)}`,
`Translation failed: ${
error instanceof Error ? error.message : String(error)
}`,
comment,
);
}
@@ -146,7 +167,7 @@ export class TranslationService {
// Batch translation of uncached comments
const chunks = chunk(uncachedComments, concurrency);
for (const chunkItems of chunks) {
try {
const textsToTranslate = chunkItems.map(item => item.comment.content);
@@ -163,13 +184,16 @@ export class TranslationService {
const result: TranslationResult = {
original: item.comment.content,
translated,
confidence: this.calculateConfidence(translated, item.comment.content),
confidence: this.calculateConfidence(
translated,
item.comment.content,
),
};
// cache results
const cacheKey = this.getCacheKey(item.comment.content);
this.cache.set(cacheKey, result);
results[item.index] = result;
} else {
// If the translation fails, an error result is created
@@ -189,8 +213,12 @@ export class TranslationService {
confidence: 0,
};
});
console.warn(`批量翻译失败: ${error instanceof Error ? error.message : String(error)}`);
console.warn(
`批量翻译失败: ${
error instanceof Error ? error.message : String(error)
}`,
);
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Source file language type
*/
@@ -183,10 +199,7 @@ export type Result<T, E = Error> =
* translation error
*/
export class TranslationError extends Error {
constructor(
message: string,
public originalComment: string,
) {
constructor(message: string, public originalComment: string) {
super(message);
this.name = 'TranslationError';
}

View File

@@ -1,8 +1,25 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Unicode Range Regular Expressions for Chinese Characters
*/
const CHINESE_REGEX = /[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]/;
const CHINESE_EXTRACT_REGEX = /[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\u3000-\u303f\uff00-\uffef]+/g;
const CHINESE_EXTRACT_REGEX =
/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\u3000-\u303f\uff00-\uffef]+/g;
/**
* Detect whether the text contains Chinese characters
@@ -31,7 +48,10 @@ export const countChineseCharacters = (text: string): number => {
/**
* Detect whether the text is mainly composed of Chinese
*/
export const isPrimarilyChinese = (text: string, threshold: number = 0.5): boolean => {
export const isPrimarilyChinese = (
text: string,
threshold: number = 0.5,
): boolean => {
const totalLength = text.length;
if (totalLength === 0) return false;
@@ -43,9 +63,9 @@ export const isPrimarilyChinese = (text: string, threshold: number = 0.5): boole
* Clean up comment text, remove comment symbols and extra spaces
*/
export const cleanCommentText = (
text: string,
commentType: 'single-line' | 'multi-line',
language?: string
text: string,
commentType: 'single-line' | 'multi-line',
language?: string,
): string => {
let cleaned = text;
@@ -100,7 +120,10 @@ export const cleanCommentText = (
/**
* Verify whether the translation result is valid.
*/
export const isValidTranslation = (original: string, translated: string): boolean => {
export const isValidTranslation = (
original: string,
translated: string,
): boolean => {
// basic verification
if (!translated || translated.trim().length === 0) {
return false;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { SourceFileLanguage, CommentPattern } from '../types/index';
/**
@@ -7,44 +23,44 @@ export const detectLanguage = (filePath: string): SourceFileLanguage => {
const ext = filePath.toLowerCase().split('.').pop();
const languageMap: Record<string, SourceFileLanguage> = {
'ts': 'typescript',
'tsx': 'typescript',
'js': 'javascript',
'jsx': 'javascript',
'go': 'go',
'md': 'markdown',
'txt': 'text',
'json': 'json',
'yaml': 'yaml',
'yml': 'yaml',
'toml': 'toml',
'ini': 'ini',
'conf': 'ini',
'config': 'ini',
'sh': 'shell',
'bash': 'shell',
'zsh': 'shell',
'fish': 'shell',
'py': 'python',
'css': 'css',
'scss': 'css',
'sass': 'css',
'less': 'css',
'html': 'html',
'htm': 'html',
'xml': 'xml',
'php': 'php',
'rb': 'ruby',
'rs': 'rust',
'java': 'java',
'c': 'c',
'h': 'c',
'cpp': 'cpp',
'cxx': 'cpp',
'cc': 'cpp',
'hpp': 'cpp',
'cs': 'csharp',
'thrift': 'thrift'
ts: 'typescript',
tsx: 'typescript',
js: 'javascript',
jsx: 'javascript',
go: 'go',
md: 'markdown',
txt: 'text',
json: 'json',
yaml: 'yaml',
yml: 'yaml',
toml: 'toml',
ini: 'ini',
conf: 'ini',
config: 'ini',
sh: 'shell',
bash: 'shell',
zsh: 'shell',
fish: 'shell',
py: 'python',
css: 'css',
scss: 'css',
sass: 'css',
less: 'css',
html: 'html',
htm: 'html',
xml: 'xml',
php: 'php',
rb: 'ruby',
rs: 'rust',
java: 'java',
c: 'c',
h: 'c',
cpp: 'cpp',
cxx: 'cpp',
cc: 'cpp',
hpp: 'cpp',
cs: 'csharp',
thrift: 'thrift',
};
return languageMap[ext || ''] || 'other';
@@ -55,19 +71,52 @@ export const detectLanguage = (filePath: string): SourceFileLanguage => {
*/
export const filterFilesByExtensions = (
files: string[],
extensions: string[]
extensions: string[],
): string[] => {
if (extensions.length === 0) {
// Default supported text file extensions
const defaultExtensions = [
'.ts', '.tsx', '.js', '.jsx', '.go', '.md', '.txt', '.json',
'.yaml', '.yml', '.toml', '.ini', '.conf', '.config',
'.sh', '.bash', '.zsh', '.fish', '.py', '.css', '.scss', '.sass', '.less',
'.html', '.htm', '.xml', '.php', '.rb', '.rs', '.java', '.c', '.h',
'.cpp', '.cxx', '.cc', '.hpp', '.cs', '.thrift'
'.ts',
'.tsx',
'.js',
'.jsx',
'.go',
'.md',
'.txt',
'.json',
'.yaml',
'.yml',
'.toml',
'.ini',
'.conf',
'.config',
'.sh',
'.bash',
'.zsh',
'.fish',
'.py',
'.css',
'.scss',
'.sass',
'.less',
'.html',
'.htm',
'.xml',
'.php',
'.rb',
'.rs',
'.java',
'.c',
'.h',
'.cpp',
'.cxx',
'.cc',
'.hpp',
'.cs',
'.thrift',
];
return files.filter(file =>
defaultExtensions.some(ext => file.toLowerCase().endsWith(ext))
defaultExtensions.some(ext => file.toLowerCase().endsWith(ext)),
);
}
@@ -85,123 +134,125 @@ export const filterFilesByExtensions = (
/**
* Obtain comment modes for different programming languages
*/
export const getCommentPatterns = (language: SourceFileLanguage): CommentPattern | null => {
export const getCommentPatterns = (
language: SourceFileLanguage,
): CommentPattern | null => {
const commentPatterns: Record<SourceFileLanguage, CommentPattern> = {
typescript: {
single: /(?:^|[^:])\s*\/\/(.*)$/gm,
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
javascript: {
single: /(?:^|[^:])\s*\/\/(.*)$/gm,
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
go: {
single: /\/\/(.*)$/gm,
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
markdown: {
single: /<!--(.*)-->/g,
multiStart: /<!--/g,
multiEnd: /-->/g
multiEnd: /-->/g,
},
text: {
single: /^(.*)$/gm, // Every line of a text file can be a comment
multiStart: /^/g,
multiEnd: /$/g
multiEnd: /$/g,
},
json: {
single: /\/\/(.*)$/gm, // JSON usually doesn't support comments, but some tools do
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
yaml: {
single: /#(.*)$/gm,
multiStart: /^$/g, // YAML does not support multi-line comments
multiEnd: /^$/g
multiEnd: /^$/g,
},
toml: {
single: /#(.*)$/gm,
multiStart: /^$/g, // TOML does not support multi-line comments
multiEnd: /^$/g
multiEnd: /^$/g,
},
ini: {
single: /[;#](.*)$/gm, // INI file support; and #as comments
multiStart: /^$/g, // INI does not support multi-line comments
multiEnd: /^$/g
multiEnd: /^$/g,
},
shell: {
single: /#(.*)$/gm,
multiStart: /^$/g, // Shell scripts do not support multi-line comments
multiEnd: /^$/g
multiEnd: /^$/g,
},
python: {
single: /#(.*)$/gm,
multiStart: /"""[\s\S]*?$/gm, // Python docstring
multiEnd: /[\s\S]*?"""/gm
multiEnd: /[\s\S]*?"""/gm,
},
css: {
single: /^$/g, // CSS does not support single-line comments
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
html: {
single: /^$/g, // HTML does not support single-line comments
multiStart: /<!--/g,
multiEnd: /-->/g
multiEnd: /-->/g,
},
xml: {
single: /^$/g, // XML does not support single-line comments
multiStart: /<!--/g,
multiEnd: /-->/g
multiEnd: /-->/g,
},
php: {
single: /(?:\/\/|#)(.*)$/gm, // PHP supports//and #as single-line comments
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
ruby: {
single: /#(.*)$/gm,
multiStart: /=begin/g,
multiEnd: /=end/g
multiEnd: /=end/g,
},
rust: {
single: /\/\/(.*)$/gm,
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
java: {
single: /\/\/(.*)$/gm,
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
c: {
single: /\/\/(.*)$/gm,
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
cpp: {
single: /\/\/(.*)$/gm,
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
csharp: {
single: /\/\/(.*)$/gm,
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
thrift: {
single: /\/\/(.*)$/gm,
multiStart: /\/\*/g,
multiEnd: /\*\//g
multiEnd: /\*\//g,
},
other: {
single: /\/\/(.*)$/gm,
multiStart: /\/\*/g,
multiEnd: /\*\//g
}
multiEnd: /\*\//g,
},
};
return commentPatterns[language] || null;
@@ -220,14 +271,47 @@ export const isSupportedFile = (filePath: string): boolean => {
*/
export const isTextFile = (filePath: string): boolean => {
const textExtensions = [
'.ts', '.tsx', '.js', '.jsx', '.go', '.md', '.txt', '.json',
'.css', '.scss', '.sass', '.less', '.html', '.htm', '.xml',
'.yaml', '.yml', '.toml', '.ini', '.conf', '.config',
'.sh', '.bash', '.zsh', '.fish', '.py', '.java', '.c', '.cpp', '.h', '.hpp', '.cs',
'.php', '.rb', '.rs', '.kt', '.swift', '.dart', '.scala', '.thrift'
'.ts',
'.tsx',
'.js',
'.jsx',
'.go',
'.md',
'.txt',
'.json',
'.css',
'.scss',
'.sass',
'.less',
'.html',
'.htm',
'.xml',
'.yaml',
'.yml',
'.toml',
'.ini',
'.conf',
'.config',
'.sh',
'.bash',
'.zsh',
'.fish',
'.py',
'.java',
'.c',
'.cpp',
'.h',
'.hpp',
'.cs',
'.php',
'.rb',
'.rs',
'.kt',
'.swift',
'.dart',
'.scala',
'.thrift',
];
return textExtensions.some(ext =>
filePath.toLowerCase().endsWith(ext)
);
return textExtensions.some(ext => filePath.toLowerCase().endsWith(ext));
};

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
Copyright (year) Beijing Volcano Engine Technology Ltd.
@@ -304,7 +320,10 @@ function queryParamsToString(params: QueryParams): string {
return undefined;
}
if (Array.isArray(val)) {
return `${escapedKey}=${val.map(uriEscape).sort().join(`&${escapedKey}=`)}`;
return `${escapedKey}=${val
.map(uriEscape)
.sort()
.join(`&${escapedKey}=`)}`;
}
return `${escapedKey}=${uriEscape(val)}`;
})

View File

@@ -19,3 +19,4 @@
"@types/node": "^18.11.9"
}
}

View File

@@ -16,3 +16,4 @@
"typescript": "~5.8.2"
}
}