@@ -845,6 +845,23 @@ function escapeRegExp(s) {
845845 return s . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
846846}
847847
848+ // 安全拼接:确保块与块之间至少保留一个换行,避免跨块黏连
849+ function joinChunksPreservingNewlines ( chunks ) {
850+ if ( ! Array . isArray ( chunks ) || chunks . length === 0 ) return '' ;
851+ let out = '' ;
852+ for ( let i = 0 ; i < chunks . length ; i ++ ) {
853+ const s = chunks [ i ] ?? '' ;
854+ if ( i === 0 ) {
855+ out = s ;
856+ continue ;
857+ }
858+ // 如果上一段不以 '\n' 结尾,则补一个 '\n'
859+ if ( ! out . endsWith ( '\n' ) ) out += '\n' ;
860+ out += s ;
861+ }
862+ return out ;
863+ }
864+
848865// 检查文件是否受保护
849866function isProtectedPath ( filePath ) {
850867 const normalizedPath = filePath . replace ( / \\ / g, '/' ) ;
@@ -1019,24 +1036,34 @@ async function translateDocumentChunks(chunks, targetLang, filePath) {
10191036 finalContent = translatedChunks [ 0 ] ;
10201037 } else {
10211038 const firstChunk = translatedChunks [ 0 ] ;
1022- const otherChunks = translatedChunks . slice ( 1 ) ;
1023-
1024- const frontMatterMatch = firstChunk . match ( / ^ - - - \n [ \s \S ] * ?\n - - - \n / ) ;
1025-
1026- if ( frontMatterMatch ) {
1027- const frontMatter = frontMatterMatch [ 0 ] ;
1028- // 不再 trim,避免吞掉空行导致行号错位
1029- const firstContent = firstChunk . replace ( frontMatterMatch [ 0 ] , '' ) ;
1030-
1031- // 直接拼接,不额外插入空行,保持逐行对齐
1032- finalContent = frontMatter + firstContent ;
1033- if ( otherChunks . length > 0 ) {
1034- finalContent += otherChunks . join ( '' ) ;
1035- }
1039+ const restChunks = translatedChunks . slice ( 1 ) ;
1040+ const fmMatch = firstChunk . match ( / ^ - - - \n [ \s \S ] * ?\n - - - \n / ) ;
1041+ if ( fmMatch ) {
1042+ const frontMatter = fmMatch [ 0 ] ;
1043+ const firstBody = firstChunk . slice ( frontMatter . length ) ;
1044+ // 保留 front matter + 正文,后续块用安全方式拼接(自动补换行)
1045+ finalContent = joinChunksPreservingNewlines ( [ frontMatter + firstBody , ...restChunks ] ) ;
10361046 } else {
1037- // 多块直接无缝拼接,避免 \n\n 造成偏移
1038- finalContent = translatedChunks . join ( '' ) ;
1047+ finalContent = joinChunksPreservingNewlines ( translatedChunks ) ;
1048+ }
1049+ }
1050+
1051+ // 整文行数与原文一致性检查(按当前文件内容)
1052+ try {
1053+ const originalTotalLines = ( await fs . readFile ( filePath , 'utf8' ) )
1054+ . toString ( )
1055+ . replace ( / \r \n / g, '\n' ) . replace ( / \r / g, '\n' )
1056+ . split ( '\n' ) . length ;
1057+ let finalTotalLines = finalContent . split ( '\n' ) . length ;
1058+ if ( finalTotalLines !== originalTotalLines ) {
1059+ console . warn ( `⚠️ 拼接后行数不一致: 原文 ${ originalTotalLines } , 译文 ${ finalTotalLines } 。尝试更保守的换行拼接。` ) ;
1060+ // 强制在块之间都插入换行(即使已有换行也不去掉)
1061+ finalContent = translatedChunks . join ( '\n' ) ;
1062+ finalTotalLines = finalContent . split ( '\n' ) . length ;
1063+ console . log ( `🧾 兜底后行数: 译文 ${ finalTotalLines } ` ) ;
10391064 }
1065+ } catch ( e ) {
1066+ console . warn ( `ℹ️ 行数兜底检查失败(非致命):${ e . message } ` ) ;
10401067 }
10411068
10421069 return finalContent ;
@@ -1058,7 +1085,9 @@ async function translateFile(filePath, targetLang) {
10581085 console . log ( `📝 翻译文件: ${ filePath } -> ${ targetLang } ` ) ;
10591086 translationStatus . total ++ ;
10601087
1061- const content = await fs . readFile ( filePath , 'utf8' ) ;
1088+ let content = await fs . readFile ( filePath , 'utf8' ) ;
1089+ // 统一换行为 LF,避免 CR 残留导致围栏/解析异常
1090+ content = content . replace ( / \r \n / g, '\n' ) . replace ( / \r / g, '\n' ) ;
10621091 console . log ( `🔍 文件大小: ${ content . length } 字符` ) ;
10631092
10641093 // Front Matter 跳过规则判断(仅 md/mdx 有意义)
0 commit comments