diff --git a/services/web/scripts/translations/checkCoverage.js b/services/web/scripts/translations/checkCoverage.js index f3562fbde7..01b31119bf 100644 --- a/services/web/scripts/translations/checkCoverage.js +++ b/services/web/scripts/translations/checkCoverage.js @@ -1,21 +1,25 @@ -const fs = require('fs') -const Path = require('path') +import fs from 'fs' +import Path from 'path' +import { fileURLToPath } from 'node:url' +import { loadLocale } from './utils.js' +const __dirname = fileURLToPath(new URL('.', import.meta.url)) const LOCALES = Path.join(__dirname, '../../locales') const SORT_BY_PROGRESS = process.argv.includes('--sort-by-progress') -function count(file) { - return Object.keys(require(Path.join(LOCALES, file))).length +function count(language) { + const locale = loadLocale(language) + return Object.keys(locale).length } async function main() { - const EN = count('en.json') + const EN = count('en') const rows = [] for (const file of await fs.promises.readdir(LOCALES)) { if (file === 'README.md') continue - const n = count(file) const name = file.replace('.json', '') + const n = count(name) rows.push({ name, done: n, @@ -29,7 +33,9 @@ async function main() { console.table(rows) } -main().catch(error => { +try { + await main() +} catch (error) { console.error(error) process.exit(1) -}) +} diff --git a/services/web/scripts/translations/checkSanitizeOptions.js b/services/web/scripts/translations/checkSanitizeOptions.js index 49bed23ed7..014f698c55 100644 --- a/services/web/scripts/translations/checkSanitizeOptions.js +++ b/services/web/scripts/translations/checkSanitizeOptions.js @@ -1,17 +1,19 @@ -const Path = require('path') -const fs = require('fs') -const { sanitize } = require('./sanitize') +import Path from 'path' +import fs from 'fs' +import Senitize from './sanitize.js' +import { fileURLToPath } from 'node:url' +import { loadLocale } from './utils.js' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) +const { sanitize } = Senitize async function main() { let ok = true const base = Path.join(__dirname, '/../../locales') for (const name of await fs.promises.readdir(base)) { if (name === 'README.md') continue - const blob = await fs.promises.readFile( - Path.join(__dirname, '/../../locales', name), - 'utf-8' - ) - const locales = JSON.parse(blob) + const language = name.replace('.json', '') + const locales = loadLocale(language) for (const key of Object.keys(locales)) { const want = locales[key] @@ -32,11 +34,10 @@ async function main() { } } -main() - .then(() => { - process.exit(0) - }) - .catch(error => { - console.error(error) - process.exit(1) - }) +try { + await main() + process.exit(0) +} catch (error) { + console.error(error) + process.exit(1) +} diff --git a/services/web/scripts/translations/checkVariables.js b/services/web/scripts/translations/checkVariables.js index e9e35047f8..bde0af9149 100644 --- a/services/web/scripts/translations/checkVariables.js +++ b/services/web/scripts/translations/checkVariables.js @@ -1,10 +1,13 @@ -const fs = require('fs') -const Path = require('path') +import fs from 'fs' +import Path from 'path' +import { fileURLToPath } from 'node:url' +import { loadLocale } from './utils.js' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) const GLOBALS = ['__appName__'] const LOCALES = Path.join(__dirname, '../../locales') -const baseLocalePath = Path.join(LOCALES, 'en.json') -const baseLocale = JSON.parse(fs.readFileSync(baseLocalePath, 'utf-8')) +const baseLocale = loadLocale('en') const baseLocaleKeys = Object.keys(baseLocale) const IGNORE_ORPHANED_TRANSLATIONS = process.argv.includes( @@ -41,9 +44,7 @@ function difference(key, base, target) { let violations = 0 for (const localeName of fs.readdirSync(LOCALES)) { if (localeName === 'README.md') continue - const localePath = Path.join(LOCALES, localeName) - - const locale = JSON.parse(fs.readFileSync(localePath, 'utf-8')) + const locale = loadLocale(localeName.replace('.json', '')) for (const key of Object.keys(locale)) { if (!baseLocaleKeys.includes(key)) { diff --git a/services/web/scripts/translations/cleanupUnusedLocales.js b/services/web/scripts/translations/cleanupUnusedLocales.js index c51246c226..e3d4d85435 100644 --- a/services/web/scripts/translations/cleanupUnusedLocales.js +++ b/services/web/scripts/translations/cleanupUnusedLocales.js @@ -1,7 +1,10 @@ -const fs = require('fs') -const Path = require('path') -const { execSync } = require('child_process') +import fs from 'fs' +import Path from 'path' +import { execSync } from 'child_process' +import { fileURLToPath } from 'node:url' +import { loadLocale } from './utils.js' +const __dirname = fileURLToPath(new URL('.', import.meta.url)) const EN_JSON = Path.join(__dirname, '../../locales/en.json') const CHECK = process.argv.includes('--check') const SYNC_NON_EN = process.argv.includes('--sync-non-en') @@ -17,7 +20,7 @@ const COUNT_SUFFIXES = [ ] async function main() { - const locales = JSON.parse(await fs.promises.readFile(EN_JSON, 'utf-8')) + const locales = loadLocale('en') const src = execSync( // - find all the app source files in web @@ -112,7 +115,7 @@ async function main() { if (name === 'README.md') continue if (name === 'en.json') continue const path = Path.join(LOCALES, name) - const locales = JSON.parse(await fs.promises.readFile(path, 'utf-8')) + const locales = loadLocale(name.replace('.json', '')) for (const key of Object.keys(locales)) { if (!found.has(key)) { delete locales[key] @@ -154,7 +157,9 @@ async function main() { await fs.promises.writeFile(EN_JSON, sorted) } -main().catch(error => { +try { + await main() +} catch (error) { console.error(error) process.exit(1) -}) +} diff --git a/services/web/scripts/translations/config.js b/services/web/scripts/translations/config.js index 8846709377..25cda68498 100644 --- a/services/web/scripts/translations/config.js +++ b/services/web/scripts/translations/config.js @@ -1,7 +1,13 @@ +import fs from 'fs' +import Path from 'path' +import { fileURLToPath } from 'url' +const __dirname = fileURLToPath(new URL('.', import.meta.url)) +const ONESKY_SETTING_PATH = Path.join(__dirname, '../../data/onesky.json') let userOptions try { - userOptions = require('../../data/onesky.json') + userOptions = JSON.parse(fs.readFileSync(ONESKY_SETTING_PATH)) } catch (err) { + if (err.code !== 'ENOENT') throw err if (!process.env.ONE_SKY_PUBLIC_KEY) { console.error( 'Cannot detect onesky credentials.\n\tDevelopers: see the docs at', @@ -24,6 +30,6 @@ function withAuth(options) { ) } -module.exports = { +export default { withAuth, } diff --git a/services/web/scripts/translations/download.js b/services/web/scripts/translations/download.js index 5905b3662c..8233516d81 100644 --- a/services/web/scripts/translations/download.js +++ b/services/web/scripts/translations/download.js @@ -1,52 +1,59 @@ -const path = require('path') -const { promises: fs } = require('fs') -const oneSky = require('@brainly/onesky-utils') -const { sanitize } = require('./sanitize') -const { withAuth } = require('./config') +import path from 'path' +import { promises as fs } from 'fs' +import oneSky from '@brainly/onesky-utils' +import Sanitize from './sanitize.js' +import Config from './config.js' +import { fileURLToPath } from 'url' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) + +const { sanitize } = Sanitize +const { withAuth } = Config 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 - const content = await oneSky.getMultilingualFile( - withAuth({ - fileName: 'en-US.json', - }) - ) - const json = JSON.parse(content) + // 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 + const content = await oneSky.getMultilingualFile( + withAuth({ + fileName: 'en-US.json', + }) + ) + const json = JSON.parse(content) - for (const [code, lang] of Object.entries(json)) { - 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 - } - - 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( - path.join(__dirname, `/../../locales/${code}.json`), - JSON.stringify( - lang.translation, - Object.keys(lang.translation).sort(), - 2 - ) + '\n' - ) + for (const [code, lang] of Object.entries(json)) { + 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 } - } catch (error) { - console.error(error) - process.exit(1) + + 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( + path.join(__dirname, `/../../locales/${code}.json`), + JSON.stringify( + lang.translation, + Object.keys(lang.translation).sort(), + 2 + ) + '\n' + ) } } -run() + +try { + await run() +} catch (error) { + console.error(error) + process.exit(1) +} diff --git a/services/web/scripts/translations/insertHTMLFragments.js b/services/web/scripts/translations/insertHTMLFragments.js index a910ed5a18..462a9d5615 100644 --- a/services/web/scripts/translations/insertHTMLFragments.js +++ b/services/web/scripts/translations/insertHTMLFragments.js @@ -13,6 +13,9 @@ localeKey: ['key1', 'key2'] click_here_to_view_sl_in_lng: ['lngName'] */ +import TransformLocales from './transformLocales.js' +import { fileURLToPath } from 'url' + const MAPPING = { support_lots_of_features: ['help_guides_link'], nothing_to_install_ready_to_go: ['start_now'], @@ -37,8 +40,6 @@ const MAPPING = { click_here_to_view_sl_in_lng: ['lngName'], } -const { transformLocales } = require('./transformLocales') - function transformLocale(locale, components) { components.forEach((key, idx) => { const i18nKey = `__${key}__` @@ -51,9 +52,12 @@ function transformLocale(locale, components) { } function main() { - transformLocales(MAPPING, transformLocale) + TransformLocales.transformLocales(MAPPING, transformLocale) } -if (require.main === module) { +if ( + fileURLToPath(import.meta.url).replace(/\.js$/, '') === + process.argv[1].replace(/\.js$/, '') +) { main() } diff --git a/services/web/scripts/translations/package.json b/services/web/scripts/translations/package.json index 82176ed7d6..81b6c885a6 100644 --- a/services/web/scripts/translations/package.json +++ b/services/web/scripts/translations/package.json @@ -4,5 +4,6 @@ "node-fetch": "^2.7.0", "sanitize-html": "^2.12.1", "yargs": "^17.7.2" - } + }, + "type": "module" } diff --git a/services/web/scripts/translations/replaceLinkFragments.js b/services/web/scripts/translations/replaceLinkFragments.js index b1e03fe39a..6f0a617d40 100644 --- a/services/web/scripts/translations/replaceLinkFragments.js +++ b/services/web/scripts/translations/replaceLinkFragments.js @@ -13,13 +13,14 @@ localeKey: ['keyLinkOpen', 'keyLinkClose'] faq_pay_by_invoice_answer: ['payByInvoiceLinkOpen', 'payByInvoiceLinkClose'] */ +import TransformLocales from './transformLocales.js' +import { fileURLToPath } from 'url' + const MAPPING = { also_provides_free_plan: ['registerLinkOpen', 'registerLinkClose'], faq_pay_by_invoice_answer: ['payByInvoiceLinkOpen', 'payByInvoiceLinkClose'], } -const { transformLocales } = require('./transformLocales') - function transformLocale(locale, [open, close]) { const i18nOpen = `__${open}__` const i18nClose = `__${close}__` @@ -30,9 +31,12 @@ function transformLocale(locale, [open, close]) { } function main() { - transformLocales(MAPPING, transformLocale) + TransformLocales.transformLocales(MAPPING, transformLocale) } -if (require.main === module) { +if ( + fileURLToPath(import.meta.url).replace(/\.js$/, '') === + process.argv[1].replace(/\.js$/, '') +) { main() } diff --git a/services/web/scripts/translations/sanitize.js b/services/web/scripts/translations/sanitize.js index af6d4d9415..3c26b221b7 100644 --- a/services/web/scripts/translations/sanitize.js +++ b/services/web/scripts/translations/sanitize.js @@ -1,4 +1,4 @@ -const sanitizeHtml = require('sanitize-html') +import sanitizeHtml from 'sanitize-html' /** * Sanitize a translation string to prevent injection attacks @@ -43,4 +43,4 @@ function sanitize(input) { ) } -module.exports = { sanitize } +export default { sanitize } diff --git a/services/web/scripts/translations/sort.js b/services/web/scripts/translations/sort.js index 86bdf19a88..e49a923597 100644 --- a/services/web/scripts/translations/sort.js +++ b/services/web/scripts/translations/sort.js @@ -1,5 +1,8 @@ -const fs = require('fs') -const Path = require('path') +import fs from 'fs' +import Path from 'path' +import { fileURLToPath } from 'url' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) const LOCALES = Path.join(__dirname, '../../locales') const CHECK = process.argv.includes('--check') @@ -30,7 +33,9 @@ async function main() { } } -main().catch(error => { +try { + await main() +} catch (error) { console.error(error) process.exit(1) -}) +} diff --git a/services/web/scripts/translations/transformLocales.js b/services/web/scripts/translations/transformLocales.js index b02c358033..2f8574ac03 100644 --- a/services/web/scripts/translations/transformLocales.js +++ b/services/web/scripts/translations/transformLocales.js @@ -1,5 +1,9 @@ -const Path = require('path') -const fs = require('fs') +import Path from 'path' +import fs from 'fs' +import { fileURLToPath } from 'node:url' +import { loadLocale } from './utils.js' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) const LANGUAGES = [ 'cs', @@ -24,7 +28,7 @@ const LANGUAGES = [ const LOCALES = {} LANGUAGES.forEach(loadLocales) function loadLocales(language) { - LOCALES[language] = require(`../../locales/${language}.json`) + LOCALES[language] = loadLocale(language) } function transformLocales(mapping, transformLocale) { @@ -45,6 +49,6 @@ function transformLocales(mapping, transformLocale) { }) } -module.exports = { +export default { transformLocales, } diff --git a/services/web/scripts/translations/translateLocales.js b/services/web/scripts/translations/translateLocales.js index 8fdd9aca37..18212b9292 100644 --- a/services/web/scripts/translations/translateLocales.js +++ b/services/web/scripts/translations/translateLocales.js @@ -1,14 +1,19 @@ -const yargs = require('yargs/yargs') -const { hideBin } = require('yargs/helpers') -const Path = require('path') -const fs = require('fs') +import yargs from 'yargs/yargs' +import { hideBin } from 'yargs/helpers' +import Path from 'path' +import fs from 'fs' +import { fileURLToPath } from 'url' +import Settings from '../../config/settings.defaults.js' +import Readline from 'readline' +import { loadLocale as loadTranslations } from './utils.js' +const __dirname = fileURLToPath(new URL('.', import.meta.url)) const LOCALES = Path.join(__dirname, '../../locales') -const VALID_LOCALES = Object.keys( - require('../../config/settings.defaults').translatedLanguages -).filter(locale => locale !== 'en') +const VALID_LOCALES = Object.keys(Settings.translatedLanguages).filter( + locale => locale !== 'en' +) -const readline = require('readline').createInterface({ +const readline = Readline.createInterface({ input: process.stdin, output: process.stdout, }) @@ -91,12 +96,6 @@ async function translateLocales() { } } -async function loadTranslations(locale) { - return JSON.parse( - fs.readFileSync(Path.join(LOCALES, `${locale}.json`), 'utf-8') - ) -} - function prompt(text) { return new Promise((resolve, reject) => readline.question(text, value => { @@ -105,11 +104,10 @@ function prompt(text) { ) } -translateLocales() - .then(() => { - process.exit(0) - }) - .catch(error => { - console.error(error) - process.exit(1) - }) +try { + await translateLocales() + process.exit(0) +} catch (error) { + console.error(error) + process.exit(1) +} diff --git a/services/web/scripts/translations/upload.js b/services/web/scripts/translations/upload.js index 942222add1..e044182e8c 100644 --- a/services/web/scripts/translations/upload.js +++ b/services/web/scripts/translations/upload.js @@ -1,8 +1,13 @@ -const Path = require('path') -const { promises: fs } = require('fs') -const { promisify } = require('util') -const oneSky = require('@brainly/onesky-utils') -const { withAuth } = require('./config') +import Path from 'path' +import { promises as fs } from 'fs' +import { promisify } from 'util' +import oneSky from '@brainly/onesky-utils' +import Config from './config.js' +import { fileURLToPath } from 'url' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) + +const { withAuth } = Config const sleep = promisify(setTimeout) @@ -58,7 +63,9 @@ async function main() { } } -main().catch(error => { +try { + await main() +} catch (error) { console.error({ error }) process.exit(1) -}) +} diff --git a/services/web/scripts/translations/utils.js b/services/web/scripts/translations/utils.js new file mode 100644 index 0000000000..e62f60db35 --- /dev/null +++ b/services/web/scripts/translations/utils.js @@ -0,0 +1,12 @@ +import { fileURLToPath } from 'url' +import fs from 'fs' +import Path from 'path' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) +const LOCALES_FOLDER = Path.join(__dirname, '../../locales') + +export function loadLocale(language) { + return JSON.parse( + fs.readFileSync(Path.join(LOCALES_FOLDER, `${language}.json`), 'utf-8') + ) +}