Просмотр исходного кода

Merge pull request #21290 from overleaf/ls-scripts-to-esm-translations

Migrate scripts/translation to esm

GitOrigin-RevId: 475ec949f0ba238791df91de109169584e68c701
Liangjun Song 1 год назад
Родитель
Сommit
8293771f58

+ 14 - 8
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 LOCALES = Path.join(__dirname, '../../locales')
 const SORT_BY_PROGRESS = process.argv.includes('--sort-by-progress')
 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() {
 async function main() {
-  const EN = count('en.json')
+  const EN = count('en')
   const rows = []
   const rows = []
 
 
   for (const file of await fs.promises.readdir(LOCALES)) {
   for (const file of await fs.promises.readdir(LOCALES)) {
     if (file === 'README.md') continue
     if (file === 'README.md') continue
-    const n = count(file)
     const name = file.replace('.json', '')
     const name = file.replace('.json', '')
+    const n = count(name)
     rows.push({
     rows.push({
       name,
       name,
       done: n,
       done: n,
@@ -29,7 +33,9 @@ async function main() {
   console.table(rows)
   console.table(rows)
 }
 }
 
 
-main().catch(error => {
+try {
+  await main()
+} catch (error) {
   console.error(error)
   console.error(error)
   process.exit(1)
   process.exit(1)
-})
+}

+ 17 - 16
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() {
 async function main() {
   let ok = true
   let ok = true
   const base = Path.join(__dirname, '/../../locales')
   const base = Path.join(__dirname, '/../../locales')
   for (const name of await fs.promises.readdir(base)) {
   for (const name of await fs.promises.readdir(base)) {
     if (name === 'README.md') continue
     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)) {
     for (const key of Object.keys(locales)) {
       const want = locales[key]
       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)
+}

+ 8 - 7
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 GLOBALS = ['__appName__']
 const LOCALES = Path.join(__dirname, '../../locales')
 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 baseLocaleKeys = Object.keys(baseLocale)
 
 
 const IGNORE_ORPHANED_TRANSLATIONS = process.argv.includes(
 const IGNORE_ORPHANED_TRANSLATIONS = process.argv.includes(
@@ -41,9 +44,7 @@ function difference(key, base, target) {
 let violations = 0
 let violations = 0
 for (const localeName of fs.readdirSync(LOCALES)) {
 for (const localeName of fs.readdirSync(LOCALES)) {
   if (localeName === 'README.md') continue
   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)) {
   for (const key of Object.keys(locale)) {
     if (!baseLocaleKeys.includes(key)) {
     if (!baseLocaleKeys.includes(key)) {

+ 12 - 7
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 EN_JSON = Path.join(__dirname, '../../locales/en.json')
 const CHECK = process.argv.includes('--check')
 const CHECK = process.argv.includes('--check')
 const SYNC_NON_EN = process.argv.includes('--sync-non-en')
 const SYNC_NON_EN = process.argv.includes('--sync-non-en')
@@ -17,7 +20,7 @@ const COUNT_SUFFIXES = [
 ]
 ]
 
 
 async function main() {
 async function main() {
-  const locales = JSON.parse(await fs.promises.readFile(EN_JSON, 'utf-8'))
+  const locales = loadLocale('en')
 
 
   const src = execSync(
   const src = execSync(
     // - find all the app source files in web
     // - find all the app source files in web
@@ -112,7 +115,7 @@ async function main() {
       if (name === 'README.md') continue
       if (name === 'README.md') continue
       if (name === 'en.json') continue
       if (name === 'en.json') continue
       const path = Path.join(LOCALES, name)
       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)) {
       for (const key of Object.keys(locales)) {
         if (!found.has(key)) {
         if (!found.has(key)) {
           delete locales[key]
           delete locales[key]
@@ -154,7 +157,9 @@ async function main() {
   await fs.promises.writeFile(EN_JSON, sorted)
   await fs.promises.writeFile(EN_JSON, sorted)
 }
 }
 
 
-main().catch(error => {
+try {
+  await main()
+} catch (error) {
   console.error(error)
   console.error(error)
   process.exit(1)
   process.exit(1)
-})
+}

+ 8 - 2
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
 let userOptions
 try {
 try {
-  userOptions = require('../../data/onesky.json')
+  userOptions = JSON.parse(fs.readFileSync(ONESKY_SETTING_PATH))
 } catch (err) {
 } catch (err) {
+  if (err.code !== 'ENOENT') throw err
   if (!process.env.ONE_SKY_PUBLIC_KEY) {
   if (!process.env.ONE_SKY_PUBLIC_KEY) {
     console.error(
     console.error(
       'Cannot detect onesky credentials.\n\tDevelopers: see the docs at',
       'Cannot detect onesky credentials.\n\tDevelopers: see the docs at',
@@ -24,6 +30,6 @@ function withAuth(options) {
   )
   )
 }
 }
 
 
-module.exports = {
+export default {
   withAuth,
   withAuth,
 }
 }

+ 51 - 44
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() {
 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 (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)
+    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')
       }
       }
-
-      await fs.writeFile(
-        path.join(__dirname, `/../../locales/${code}.json`),
-        JSON.stringify(
-          lang.translation,
-          Object.keys(lang.translation).sort(),
-          2
-        ) + '\n'
-      )
+      lang.translation[key] = sanitize(value)
     }
     }
-  } catch (error) {
-    console.error(error)
-    process.exit(1)
+
+    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)
+}

+ 8 - 4
services/web/scripts/translations/insertHTMLFragments.js

@@ -13,6 +13,9 @@
   localeKey: ['key1', 'key2']
   localeKey: ['key1', 'key2']
   click_here_to_view_sl_in_lng: ['lngName']
   click_here_to_view_sl_in_lng: ['lngName']
  */
  */
+import TransformLocales from './transformLocales.js'
+import { fileURLToPath } from 'url'
+
 const MAPPING = {
 const MAPPING = {
   support_lots_of_features: ['help_guides_link'],
   support_lots_of_features: ['help_guides_link'],
   nothing_to_install_ready_to_go: ['start_now'],
   nothing_to_install_ready_to_go: ['start_now'],
@@ -37,8 +40,6 @@ const MAPPING = {
   click_here_to_view_sl_in_lng: ['lngName'],
   click_here_to_view_sl_in_lng: ['lngName'],
 }
 }
 
 
-const { transformLocales } = require('./transformLocales')
-
 function transformLocale(locale, components) {
 function transformLocale(locale, components) {
   components.forEach((key, idx) => {
   components.forEach((key, idx) => {
     const i18nKey = `__${key}__`
     const i18nKey = `__${key}__`
@@ -51,9 +52,12 @@ function transformLocale(locale, components) {
 }
 }
 
 
 function main() {
 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()
   main()
 }
 }

+ 2 - 1
services/web/scripts/translations/package.json

@@ -4,5 +4,6 @@
     "node-fetch": "^2.7.0",
     "node-fetch": "^2.7.0",
     "sanitize-html": "^2.12.1",
     "sanitize-html": "^2.12.1",
     "yargs": "^17.7.2"
     "yargs": "^17.7.2"
-  }
+  },
+  "type": "module"
 }
 }

+ 8 - 4
services/web/scripts/translations/replaceLinkFragments.js

@@ -13,13 +13,14 @@
   localeKey: ['keyLinkOpen', 'keyLinkClose']
   localeKey: ['keyLinkOpen', 'keyLinkClose']
   faq_pay_by_invoice_answer: ['payByInvoiceLinkOpen', 'payByInvoiceLinkClose']
   faq_pay_by_invoice_answer: ['payByInvoiceLinkOpen', 'payByInvoiceLinkClose']
  */
  */
+import TransformLocales from './transformLocales.js'
+import { fileURLToPath } from 'url'
+
 const MAPPING = {
 const MAPPING = {
   also_provides_free_plan: ['registerLinkOpen', 'registerLinkClose'],
   also_provides_free_plan: ['registerLinkOpen', 'registerLinkClose'],
   faq_pay_by_invoice_answer: ['payByInvoiceLinkOpen', 'payByInvoiceLinkClose'],
   faq_pay_by_invoice_answer: ['payByInvoiceLinkOpen', 'payByInvoiceLinkClose'],
 }
 }
 
 
-const { transformLocales } = require('./transformLocales')
-
 function transformLocale(locale, [open, close]) {
 function transformLocale(locale, [open, close]) {
   const i18nOpen = `__${open}__`
   const i18nOpen = `__${open}__`
   const i18nClose = `__${close}__`
   const i18nClose = `__${close}__`
@@ -30,9 +31,12 @@ function transformLocale(locale, [open, close]) {
 }
 }
 
 
 function main() {
 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()
   main()
 }
 }

+ 2 - 2
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
  * Sanitize a translation string to prevent injection attacks
@@ -43,4 +43,4 @@ function sanitize(input) {
   )
   )
 }
 }
 
 
-module.exports = { sanitize }
+export default { sanitize }

+ 9 - 4
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 LOCALES = Path.join(__dirname, '../../locales')
 const CHECK = process.argv.includes('--check')
 const CHECK = process.argv.includes('--check')
@@ -30,7 +33,9 @@ async function main() {
   }
   }
 }
 }
 
 
-main().catch(error => {
+try {
+  await main()
+} catch (error) {
   console.error(error)
   console.error(error)
   process.exit(1)
   process.exit(1)
-})
+}

+ 8 - 4
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 = [
 const LANGUAGES = [
   'cs',
   'cs',
@@ -24,7 +28,7 @@ const LANGUAGES = [
 const LOCALES = {}
 const LOCALES = {}
 LANGUAGES.forEach(loadLocales)
 LANGUAGES.forEach(loadLocales)
 function loadLocales(language) {
 function loadLocales(language) {
-  LOCALES[language] = require(`../../locales/${language}.json`)
+  LOCALES[language] = loadLocale(language)
 }
 }
 
 
 function transformLocales(mapping, transformLocale) {
 function transformLocales(mapping, transformLocale) {
@@ -45,6 +49,6 @@ function transformLocales(mapping, transformLocale) {
   })
   })
 }
 }
 
 
-module.exports = {
+export default {
   transformLocales,
   transformLocales,
 }
 }

+ 20 - 22
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 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,
   input: process.stdin,
   output: process.stdout,
   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) {
 function prompt(text) {
   return new Promise((resolve, reject) =>
   return new Promise((resolve, reject) =>
     readline.question(text, value => {
     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)
+}

+ 14 - 7
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)
 const sleep = promisify(setTimeout)
 
 
@@ -58,7 +63,9 @@ async function main() {
   }
   }
 }
 }
 
 
-main().catch(error => {
+try {
+  await main()
+} catch (error) {
   console.error({ error })
   console.error({ error })
   process.exit(1)
   process.exit(1)
-})
+}

+ 12 - 0
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')
+  )
+}