@@ -169,9 +169,6 @@ class Mandarin {
169169 this . parseMarkdownFile = universalify
170170 . fromPromise ( this . parseMarkdownFile )
171171 . bind ( this ) ;
172- this . parseMarkdownFileText = universalify
173- . fromPromise ( this . parseMarkdownFileText )
174- . bind ( this ) ;
175172 this . getLocalizedMarkdownFileName = universalify
176173 . fromPromise ( this . getLocalizedMarkdownFileName )
177174 . bind ( this ) ;
@@ -187,30 +184,6 @@ class Mandarin {
187184 } ) ;
188185 }
189186
190- // Improved method for text-based markdown translation using Promise.all and p-map
191- async parseMarkdownFileText ( content , locale ) {
192- // debug('parseMarkdownFileText', filePath);
193- // const content = await readFile(filePath, 'utf8');
194-
195- // don't translate the main file.md file, only for other locales
196- const locales = this . config . i18n . config . locales . filter (
197- ( locale ) => locale !== this . config . i18n . config . defaultLocale
198- ) ;
199-
200- // Use Promise.all for parallel processing of all locales
201- await Promise . all (
202- locales . map ( async ( locale ) => {
203- if ( locale === 'en' ) return ; // Skip English as it's typically the source
204-
205- const translatedContent = await this . translateMarkdownContent ( content , locale ) ;
206- const localizedFilePath = this . getLocalizedMarkdownFileName ( filePath , locale ) ;
207-
208- debug ( 'writing file' , localizedFilePath ) ;
209- await writeFile ( localizedFilePath , translatedContent ) ;
210- } )
211- ) ;
212- }
213-
214187 // Helper method to detect and extract tables from content
215188 detectTables ( content ) {
216189 const lines = content . split ( '\n' ) ;
@@ -502,25 +475,45 @@ class Mandarin {
502475 return text ;
503476 }
504477
478+ // Protect placeholders within text by temporarily replacing them
479+ const placeholderMap = new Map ( ) ;
480+ let placeholderCounter = 0 ;
481+
482+ // Find all placeholders in the text
483+ const placeholderPattern = / _ _ P R O T E C T E D _ \w + _ \d + _ _ / g;
484+ let textToTranslate = text ;
485+
486+ // Replace placeholders with temporary safe tokens
487+ textToTranslate = textToTranslate . replace ( placeholderPattern , ( match ) => {
488+ const tempToken = `TEMP_PLACEHOLDER_${ placeholderCounter } ` ;
489+ placeholderMap . set ( tempToken , match ) ;
490+ placeholderCounter ++ ;
491+ return tempToken ;
492+ } ) ;
493+
494+ // If the text is now empty or only contains temp tokens, don't translate
495+ if ( ! textToTranslate . trim ( ) || textToTranslate . match ( / ^ T E M P _ P L A C E H O L D E R _ \d + $ / ) ) {
496+ return text ;
497+ }
498+
505499 // Check cache first
506- const key = `${ locale } :${ revHash ( text ) } ` ;
500+ const key = `${ locale } :${ revHash ( textToTranslate ) } ` ;
507501 let translation ;
508502
509503 if ( this . redisClient ) {
510504 translation = await this . redisClient . get ( key ) ;
511505 }
512506
513507 if ( ! _ . isString ( translation ) ) {
514- debug ( 'getting translation for text:' , text . substring ( 0 , 50 ) + '...' ) ;
508+ debug ( 'getting translation for text:' , textToTranslate . substring ( 0 , 50 ) + '...' ) ;
515509 try {
516- // Use format: 'text' to preserve formatting
517- [ translation ] = await this . client . translate ( text , {
510+ [ translation ] = await this . client . translate ( textToTranslate , {
518511 to : locale ,
519512 format : 'text'
520513 } ) ;
521514 } catch ( err ) {
522515 debug ( 'translation error:' , err ) ;
523- return text ; // Return original text if translation fails
516+ return text ;
524517 }
525518
526519 if ( _ . isString ( translation ) ) {
@@ -531,8 +524,16 @@ class Mandarin {
531524 }
532525 }
533526
534- // Don't replace pipe characters for table content - this was the main issue
535- return _ . isString ( translation ) ? translation : text ;
527+ if ( _ . isString ( translation ) ) {
528+ // Restore the original placeholders
529+ placeholderMap . forEach ( ( originalPlaceholder , tempToken ) => {
530+ translation = translation . replace ( new RegExp ( tempToken , 'g' ) , originalPlaceholder ) ;
531+ } ) ;
532+
533+ return translation ;
534+ }
535+
536+ return text ;
536537 }
537538
538539 async parseMarkdownFile ( filePath ) {
@@ -545,51 +546,61 @@ class Mandarin {
545546 ( locale ) => locale !== this . config . i18n . config . defaultLocale
546547 ) ;
547548
548- const files = await Promise . all (
549- locales . map ( ( locale ) => {
550- return new Promise ( ( resolve , reject ) => {
551- unified ( )
552- // <https://unifiedjs.com/learn/recipe/remark-html/#how-to-properly-support-html-inside-markdown>
553- . use ( remarkPresetGitHub )
554- . use ( remarkParse )
555- . use ( slug )
556- /*
557- .use(autoLinkHeadings, {
558- behavior: 'prepend', // Use 'prepend' or 'append', but NOT 'wrap'.
559- // The content for the new, separate link.
560- content: {
561- type: 'text',
562- value: '🔗', // Using an emoji for the link content.
563- },
564- // Link properties can be added if needed, e.g., for CSS classes.
565- properties: {
566- ariaHidden: 'true',
567- class: 'anchor'
568- }
569- })
570- */
571- . use ( addCustomIdToHeadingText )
572- . use ( remarkStringify , {
573- // Important: This option prevents the processor from escaping the `{` and `}`
574- // characters in our custom ID.
575- fences : true
576- } )
577- . process ( markdown , ( err , file ) => {
578- if ( err ) return reject ( err ) ;
579- resolve ( { locale, content : String ( file ) } ) ;
580- } ) ;
549+ const content = await new Promise ( ( resolve , reject ) => {
550+ unified ( )
551+ // <https://unifiedjs.com/learn/recipe/remark-html/#how-to-properly-support-html-inside-markdown>
552+ . use ( remarkPresetGitHub )
553+ . use ( remarkParse )
554+ . use ( slug )
555+ /*
556+ .use(autoLinkHeadings, {
557+ behavior: 'prepend', // Use 'prepend' or 'append', but NOT 'wrap'.
558+ // The content for the new, separate link.
559+ content: {
560+ type: 'text',
561+ value: '🔗', // Using an emoji for the link content.
562+ },
563+ // Link properties can be added if needed, e.g., for CSS classes.
564+ properties: {
565+ ariaHidden: 'true',
566+ class: 'anchor'
567+ }
568+ })
569+ */
570+ . use ( addCustomIdToHeadingText )
571+ . use ( remarkStringify , {
572+ // Important: This option prevents the processor from escaping the `{` and `}`
573+ // characters in our custom ID.
574+ fences : true
575+ } )
576+ . process ( markdown , ( err , file ) => {
577+ if ( err ) return reject ( err ) ;
578+ resolve ( String ( file ) ) ;
581579 } ) ;
582- } )
583- ) ;
580+ } ) ;
581+
584582 await Promise . all (
585- files . map ( async ( file ) => {
583+ locales . map ( async ( locale ) => {
586584 const localizedFilePath = this . getLocalizedMarkdownFileName (
587585 filePath ,
588- file . locale
586+ locale
589587 ) ;
590- const translatedContent = await this . translateMarkdownContent ( file . content , file . locale ) ;
588+ let result = await this . translateMarkdownContent ( content , locale ) ;
589+
590+ // Fix RTL reordering for headings with custom IDs
591+ result = result . replace (
592+ / ^ ( .+ ?) \s + ( # { 1 , 6 } ) \s + ( \{ # [ ^ } ] + \} ) $ / gm,
593+ '$2 $1 $3'
594+ ) ;
595+
596+ // Fix RTL reordering for headings without custom IDs
597+ result = result . replace (
598+ / ^ ( .+ ?) \s + ( # { 1 , 6 } ) $ / gm,
599+ '$2 $1'
600+ ) ;
601+
591602 debug ( 'writing file' , localizedFilePath ) ;
592- await writeFile ( localizedFilePath , translatedContent ) ;
603+ await writeFile ( localizedFilePath , result ) ;
593604 } )
594605 ) ;
595606 }
@@ -702,7 +713,7 @@ class Mandarin {
702713
703714 // get the translation results from Google
704715 if ( ! _ . isString ( translation ) ) {
705- debug ( 'getting translation' , key ) ;
716+ debug ( 'getting translation' , key , phrase ) ;
706717 try {
707718 [ translation ] = await this . client . translate ( phrase , {
708719 to : locale ,
0 commit comments