| 
									
										
										
										
											2021-02-03 10:46:27 +01:00
										 |  |  | const { isEmpty, cloneDeep } = require('lodash'); | 
					
						
							| 
									
										
										
										
											2020-02-24 15:05:18 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | exports.translateComments = (text, lang, dict, codeLang) => { | 
					
						
							|  |  |  |   const knownComments = Object.keys(dict); | 
					
						
							|  |  |  |   const config = { knownComments, dict, lang }; | 
					
						
							|  |  |  |   switch (codeLang) { | 
					
						
							|  |  |  |     case 'js': | 
					
						
							|  |  |  |     case 'jsx': | 
					
						
							| 
									
										
										
										
											2020-09-28 15:13:18 +02:00
										 |  |  |       return transMultiline(transInline(text, config), config); | 
					
						
							| 
									
										
										
										
											2020-02-24 15:05:18 +01:00
										 |  |  |     case 'html': | 
					
						
							| 
									
										
										
										
											2020-09-28 15:13:18 +02:00
										 |  |  |       return transScript(transHTML(transCSS(text, config), config), config); | 
					
						
							| 
									
										
										
										
											2020-02-24 15:05:18 +01:00
										 |  |  |     default: | 
					
						
							|  |  |  |       return text; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-09 18:08:38 +02:00
										 |  |  | exports.translateCommentsInChallenge = (challenge, lang, dict) => { | 
					
						
							| 
									
										
										
										
											2021-02-03 10:46:27 +01:00
										 |  |  |   const challClone = cloneDeep(challenge); | 
					
						
							| 
									
										
										
										
											2020-09-28 15:13:18 +02:00
										 |  |  |   if (!challClone.files) { | 
					
						
							|  |  |  |     console.warn(`Challenge ${challClone.title} has no comments to translate`); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     Object.keys(challClone.files).forEach(key => { | 
					
						
							|  |  |  |       if (challClone.files[key].contents) { | 
					
						
							|  |  |  |         challClone.files[key].contents = this.translateComments( | 
					
						
							|  |  |  |           challenge.files[key].contents, | 
					
						
							|  |  |  |           lang, | 
					
						
							|  |  |  |           dict, | 
					
						
							|  |  |  |           challClone.files[key].ext | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-02-24 15:05:18 +01:00
										 |  |  |   return challClone; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | exports.mergeChallenges = (engChal, transChal) => { | 
					
						
							| 
									
										
										
										
											2020-08-13 12:25:53 +02:00
										 |  |  |   const hasTests = | 
					
						
							|  |  |  |     (engChal.tests && transChal.tests) || | 
					
						
							|  |  |  |     (engChal.question && transChal.question); | 
					
						
							| 
									
										
										
										
											2020-03-05 14:13:56 +01:00
										 |  |  |   const challenge = { | 
					
						
							| 
									
										
										
										
											2020-02-24 15:05:18 +01:00
										 |  |  |     ...engChal, | 
					
						
							|  |  |  |     description: transChal.description, | 
					
						
							|  |  |  |     instructions: transChal.instructions, | 
					
						
							| 
									
										
										
										
											2020-10-01 15:50:43 +02:00
										 |  |  |     originalTitle: engChal.title, | 
					
						
							|  |  |  |     // TODO: throw in production?
 | 
					
						
							|  |  |  |     title: isEmpty(transChal.title) ? engChal.title : transChal.title, | 
					
						
							| 
									
										
										
										
											2020-08-13 12:25:53 +02:00
										 |  |  |     forumTopicId: transChal.forumTopicId | 
					
						
							| 
									
										
										
										
											2020-02-24 15:05:18 +01:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2020-08-13 12:25:53 +02:00
										 |  |  |   if (!hasTests) | 
					
						
							|  |  |  |     throw Error( | 
					
						
							|  |  |  |       `Both challenges must have tests or questions.
 | 
					
						
							|  |  |  |       title: ${engChal.title} | 
					
						
							| 
									
										
										
										
											2020-10-01 15:50:43 +02:00
										 |  |  |       translated title: ${transChal.title}`
 | 
					
						
							| 
									
										
										
										
											2020-08-13 12:25:53 +02:00
										 |  |  |     ); | 
					
						
							|  |  |  |   // TODO: this should break the build when we go to production, but
 | 
					
						
							|  |  |  |   // not for testing.
 | 
					
						
							|  |  |  |   if (transChal.tests && transChal.tests.length !== engChal.tests.length) { | 
					
						
							|  |  |  |     console.error( | 
					
						
							|  |  |  |       `Challenges in both languages must have the same number of tests.
 | 
					
						
							|  |  |  |     title: ${engChal.title} | 
					
						
							| 
									
										
										
										
											2020-10-01 15:50:43 +02:00
										 |  |  |     translated title: ${transChal.title}`
 | 
					
						
							| 
									
										
										
										
											2020-08-13 12:25:53 +02:00
										 |  |  |     ); | 
					
						
							|  |  |  |     return challenge; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // throw Error(
 | 
					
						
							|  |  |  |   //   `Challenges in both languages must have the same number of tests.
 | 
					
						
							|  |  |  |   // title: ${engChal.title}
 | 
					
						
							| 
									
										
										
										
											2020-10-01 15:50:43 +02:00
										 |  |  |   // translated title: ${transChal.title}`
 | 
					
						
							| 
									
										
										
										
											2020-08-13 12:25:53 +02:00
										 |  |  |   // );
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (transChal.tests) { | 
					
						
							|  |  |  |     const translatedTests = | 
					
						
							|  |  |  |       engChal.challengeType === 7 | 
					
						
							|  |  |  |         ? transChal.tests.map(({ title }, i) => ({ | 
					
						
							|  |  |  |             title, | 
					
						
							|  |  |  |             id: engChal.tests[i].id | 
					
						
							|  |  |  |           })) | 
					
						
							|  |  |  |         : transChal.tests.map(({ text }, i) => ({ | 
					
						
							|  |  |  |             text, | 
					
						
							|  |  |  |             testString: engChal.tests[i].testString | 
					
						
							|  |  |  |           })); | 
					
						
							|  |  |  |     challenge.tests = translatedTests; | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     challenge.question = transChal.question; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-05 14:13:56 +01:00
										 |  |  |   // certificates do not have forumTopicIds
 | 
					
						
							|  |  |  |   if (challenge.challengeType === 7) delete challenge.forumTopicId; | 
					
						
							|  |  |  |   return challenge; | 
					
						
							| 
									
										
										
										
											2020-02-24 15:05:18 +01:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // bare urls could be interpreted as comments, so we have to lookbehind for
 | 
					
						
							|  |  |  | // http:// or https://
 | 
					
						
							|  |  |  | function transInline(text, config) { | 
					
						
							| 
									
										
										
										
											2020-09-28 15:13:18 +02:00
										 |  |  |   return translateGeneric(text, config, '((?<!https?:)//\\s*)', '(\\s*$)'); | 
					
						
							| 
									
										
										
										
											2020-02-24 15:05:18 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function transMultiline(text, config) { | 
					
						
							|  |  |  |   return translateGeneric(text, config, '(/\\*\\s*)', '(\\s*\\*/)'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CSS has to be handled separately since it is looking for comments inside tags
 | 
					
						
							|  |  |  | function transCSS(text, config) { | 
					
						
							|  |  |  |   const regex = /<style>.*?<\/style>/gms; | 
					
						
							|  |  |  |   const matches = text.matchAll(regex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (const [match] of matches) { | 
					
						
							|  |  |  |     text = text.replace(match, transMultiline(match, config)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return text; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-28 15:13:18 +02:00
										 |  |  | function transScript(text, config) { | 
					
						
							|  |  |  |   const regex = /<script>.*?<\/script>/gms; | 
					
						
							|  |  |  |   const matches = text.matchAll(regex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (const [match] of matches) { | 
					
						
							|  |  |  |     text = text.replace( | 
					
						
							|  |  |  |       match, | 
					
						
							|  |  |  |       transMultiline(transInline(match, config), config) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return text; | 
					
						
							| 
									
										
										
										
											2020-02-24 15:05:18 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function transHTML(text, config) { | 
					
						
							|  |  |  |   return translateGeneric(text, config, '(<!--\\s*)', '(\\s*-->)'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function translateGeneric(text, config, regexBefore, regexAfter) { | 
					
						
							|  |  |  |   const { knownComments, dict, lang } = config; | 
					
						
							|  |  |  |   const regex = new RegExp(regexBefore + '(.*?)' + regexAfter, 'gms'); | 
					
						
							|  |  |  |   const matches = text.matchAll(regex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (const [match, before, comment, after] of matches) { | 
					
						
							|  |  |  |     if (knownComments.includes(comment)) { | 
					
						
							|  |  |  |       text = text.replace(match, `${before}${dict[comment][lang]}${after}`); | 
					
						
							| 
									
										
										
										
											2021-02-03 10:43:32 +01:00
										 |  |  |     } else if (comment.trim()) { | 
					
						
							|  |  |  |       throw `${comment} does not appear in the comment dictionary`; | 
					
						
							| 
									
										
										
										
											2020-02-24 15:05:18 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return text; | 
					
						
							|  |  |  | } |