2020-08-18 06:09:28 -04:00
|
|
|
|
const { promises: fs } = require('fs')
|
|
|
|
|
const oneSky = require('@brainly/onesky-utils')
|
|
|
|
|
const sanitizeHtml = require('sanitize-html')
|
2020-09-28 06:51:39 -04:00
|
|
|
|
const { withAuth } = require('./config')
|
2020-08-18 06:09:28 -04:00
|
|
|
|
|
|
|
|
|
async function run() {
|
|
|
|
|
try {
|
|
|
|
|
// The recommended OneSky set-up appears to require an API request to
|
|
|
|
|
// generate files on their side, which you could then request and use. We
|
|
|
|
|
// only have 1 such file that appears to be misnamed (en-US, despite our
|
|
|
|
|
// translations being marked as GB) and very out-of-date.
|
|
|
|
|
// However by requesting the "multilingual file" for this file, we get all
|
|
|
|
|
// of the translations
|
2020-09-28 06:51:39 -04:00
|
|
|
|
const content = await oneSky.getMultilingualFile(
|
|
|
|
|
withAuth({
|
2021-04-27 03:52:58 -04:00
|
|
|
|
fileName: 'en-US.json',
|
2020-09-28 06:51:39 -04:00
|
|
|
|
})
|
|
|
|
|
)
|
2020-08-18 06:09:28 -04:00
|
|
|
|
const json = JSON.parse(content)
|
|
|
|
|
|
|
|
|
|
for (const [code, lang] of Object.entries(json)) {
|
2020-10-21 04:35:32 -04:00
|
|
|
|
if (code === 'en-GB') {
|
|
|
|
|
// OneSky does not have read-after-write consistency.
|
|
|
|
|
// Skip the dump of English locales, which may not include locales
|
|
|
|
|
// that were just uploaded.
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 06:09:28 -04:00
|
|
|
|
for (let [key, value] of Object.entries(lang.translation)) {
|
|
|
|
|
// Handle multi-line strings as arrays by joining on newline
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
value = value.join('\n')
|
|
|
|
|
}
|
|
|
|
|
lang.translation[key] = sanitize(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await fs.writeFile(
|
2020-10-21 04:35:32 -04:00
|
|
|
|
`${__dirname}/../../locales/${code}.json`,
|
2020-09-29 10:30:16 -04:00
|
|
|
|
JSON.stringify(lang.translation, null, 2) + '\n'
|
2020-08-18 06:09:28 -04:00
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error)
|
|
|
|
|
process.exit(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
run()
|
|
|
|
|
|
2020-09-03 09:45:29 -04:00
|
|
|
|
/**
|
|
|
|
|
* Sanitize a translation string to prevent injection attacks
|
|
|
|
|
*
|
|
|
|
|
* @param {string} input
|
|
|
|
|
* @returns {string}
|
|
|
|
|
*/
|
2020-08-18 06:09:28 -04:00
|
|
|
|
function sanitize(input) {
|
2021-08-16 10:59:04 -04:00
|
|
|
|
// Block Angular XSS
|
|
|
|
|
// Ticket: https://github.com/overleaf/issues/issues/4478
|
|
|
|
|
input = input.replace(/'/g, '’')
|
|
|
|
|
// Use left quote where (likely) appropriate.
|
|
|
|
|
input.replace(/ ’/g, ' ‘')
|
|
|
|
|
|
2020-08-18 06:09:28 -04:00
|
|
|
|
return sanitizeHtml(input, {
|
2020-09-03 09:45:29 -04:00
|
|
|
|
// Allow "replacement" tags (in the format <0>, <1>, <2>, etc) used by
|
|
|
|
|
// react-i18next to allow for HTML insertion via the Trans component.
|
|
|
|
|
// See: https://github.com/overleaf/developer-manual/blob/master/code/translations.md
|
|
|
|
|
// Unfortunately the sanitizeHtml library does not accept regexes or a
|
|
|
|
|
// function for the allowedTags option, so we are limited to a hard-coded
|
|
|
|
|
// number of "replacement" tags.
|
|
|
|
|
allowedTags: ['b', 'strong', 'a', 'code', ...range(10)],
|
2020-08-18 06:09:28 -04:00
|
|
|
|
allowedAttributes: {
|
2021-04-27 03:52:58 -04:00
|
|
|
|
a: ['href', 'class'],
|
2020-08-18 06:09:28 -04:00
|
|
|
|
},
|
|
|
|
|
textFilter(text) {
|
|
|
|
|
return text
|
|
|
|
|
.replace(/\{\{/, '{{')
|
|
|
|
|
.replace(/\}\}/, '}}')
|
2021-04-27 03:52:58 -04:00
|
|
|
|
},
|
2020-08-18 06:09:28 -04:00
|
|
|
|
})
|
|
|
|
|
}
|
2020-09-03 09:45:29 -04:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generate a range of numbers as strings up to the given size
|
|
|
|
|
*
|
|
|
|
|
* @param {number} size Size of range
|
|
|
|
|
* @returns {string[]}
|
|
|
|
|
*/
|
|
|
|
|
function range(size) {
|
|
|
|
|
return Array.from(Array(size).keys()).map(n => n.toString())
|
|
|
|
|
}
|