download.js 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. const { promises: fs } = require('fs')
  2. const oneSky = require('@brainly/onesky-utils')
  3. const sanitizeHtml = require('sanitize-html')
  4. const { withAuth } = require('./config')
  5. async function run() {
  6. try {
  7. // The recommended OneSky set-up appears to require an API request to
  8. // generate files on their side, which you could then request and use. We
  9. // only have 1 such file that appears to be misnamed (en-US, despite our
  10. // translations being marked as GB) and very out-of-date.
  11. // However by requesting the "multilingual file" for this file, we get all
  12. // of the translations
  13. const content = await oneSky.getMultilingualFile(
  14. withAuth({
  15. fileName: 'en-US.json'
  16. })
  17. )
  18. const json = JSON.parse(content)
  19. for (const [code, lang] of Object.entries(json)) {
  20. if (code === 'en-GB') {
  21. // OneSky does not have read-after-write consistency.
  22. // Skip the dump of English locales, which may not include locales
  23. // that were just uploaded.
  24. continue
  25. }
  26. for (let [key, value] of Object.entries(lang.translation)) {
  27. // Handle multi-line strings as arrays by joining on newline
  28. if (Array.isArray(value)) {
  29. value = value.join('\n')
  30. }
  31. lang.translation[key] = sanitize(value)
  32. }
  33. await fs.writeFile(
  34. `${__dirname}/../../locales/${code}.json`,
  35. JSON.stringify(lang.translation, null, 2) + '\n'
  36. )
  37. }
  38. } catch (error) {
  39. console.error(error)
  40. process.exit(1)
  41. }
  42. }
  43. run()
  44. /**
  45. * Sanitize a translation string to prevent injection attacks
  46. *
  47. * @param {string} input
  48. * @returns {string}
  49. */
  50. function sanitize(input) {
  51. return sanitizeHtml(input, {
  52. // Allow "replacement" tags (in the format <0>, <1>, <2>, etc) used by
  53. // react-i18next to allow for HTML insertion via the Trans component.
  54. // See: https://github.com/overleaf/developer-manual/blob/master/code/translations.md
  55. // Unfortunately the sanitizeHtml library does not accept regexes or a
  56. // function for the allowedTags option, so we are limited to a hard-coded
  57. // number of "replacement" tags.
  58. allowedTags: ['b', 'strong', 'a', 'code', ...range(10)],
  59. allowedAttributes: {
  60. a: ['href', 'class']
  61. },
  62. textFilter(text) {
  63. return text
  64. .replace(/\{\{/, '&#123;&#123;')
  65. .replace(/\}\}/, '&#125;&#125;')
  66. }
  67. })
  68. }
  69. /**
  70. * Generate a range of numbers as strings up to the given size
  71. *
  72. * @param {number} size Size of range
  73. * @returns {string[]}
  74. */
  75. function range(size) {
  76. return Array.from(Array(size).keys()).map(n => n.toString())
  77. }