translateLocales.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import yargs from 'yargs/yargs'
  2. import { hideBin } from 'yargs/helpers'
  3. import Path from 'path'
  4. import fs from 'fs'
  5. import { fileURLToPath } from 'url'
  6. import Settings from '../../config/settings.defaults.js'
  7. import Readline from 'readline'
  8. import { loadLocale as loadTranslations } from './utils.js'
  9. const __dirname = fileURLToPath(new URL('.', import.meta.url))
  10. const LOCALES = Path.join(__dirname, '../../locales')
  11. const VALID_LOCALES = Object.keys(Settings.translatedLanguages).filter(
  12. locale => locale !== 'en'
  13. )
  14. const readline = Readline.createInterface({
  15. input: process.stdin,
  16. output: process.stdout,
  17. })
  18. const argv = yargs(hideBin(process.argv))
  19. .usage('Translate locales')
  20. .option('locale', {
  21. alias: 'l',
  22. type: 'string',
  23. required: 'true',
  24. description: 'Target 2-letter locale code',
  25. choices: VALID_LOCALES,
  26. })
  27. .option('skip-until', {
  28. type: 'string',
  29. description: 'Skip locales until after the provided key',
  30. })
  31. .parse()
  32. async function translateLocales() {
  33. let { locale, skip } = argv
  34. console.log(`Looking for missing [${locale}] translations...`)
  35. const keysToUploadFolder = Path.join(__dirname, `translated-keys-to-upload`)
  36. if (!fs.existsSync(keysToUploadFolder)) {
  37. fs.mkdirSync(keysToUploadFolder)
  38. }
  39. const localeKeysToUploadPath = Path.join(keysToUploadFolder, `${locale}.json`)
  40. const keysToUpload = fs.existsSync(localeKeysToUploadPath)
  41. ? JSON.parse(fs.readFileSync(localeKeysToUploadPath, 'utf-8'))
  42. : []
  43. const englishTranslations = await loadTranslations('en')
  44. const englishKeys = Object.keys(englishTranslations)
  45. const localeTranslations = await loadTranslations(locale)
  46. const translatedKeys = Object.keys(localeTranslations)
  47. console.log(
  48. `Currently translated: ${translatedKeys.length} / ${englishKeys.length}`
  49. )
  50. for (const key of englishKeys) {
  51. if (skip) {
  52. if (key === skip) skip = ''
  53. continue
  54. }
  55. const translation = localeTranslations[key]
  56. if (!translation || translation.length === 0) {
  57. let value = await prompt(
  58. `\nMissing translation for: ${key}\n"${englishTranslations[key]}"\n`
  59. )
  60. while (value.includes("'")) {
  61. value = await prompt(
  62. `\nTranslations should not contain single-quote characters, please use curvy quotes (‘ or ’) instead:\n`
  63. )
  64. }
  65. if (!value) {
  66. console.log(`Skipping ${key}`)
  67. continue
  68. }
  69. localeTranslations[key] = value
  70. const path = Path.join(LOCALES, `${locale}.json`)
  71. const sorted =
  72. JSON.stringify(
  73. localeTranslations,
  74. Object.keys(localeTranslations).sort(),
  75. 2
  76. ) + '\n'
  77. fs.writeFileSync(path, sorted)
  78. keysToUpload.push(key)
  79. const formattedKeysToUpload =
  80. JSON.stringify(Array.from(new Set(keysToUpload)), null, 2) + '\n'
  81. fs.writeFileSync(localeKeysToUploadPath, formattedKeysToUpload)
  82. console.log(`"${key}": "${value}" added to ${locale}.json`)
  83. }
  84. }
  85. }
  86. function prompt(text) {
  87. return new Promise((resolve, reject) =>
  88. readline.question(text, value => {
  89. resolve(value)
  90. })
  91. )
  92. }
  93. try {
  94. await translateLocales()
  95. process.exit(0)
  96. } catch (error) {
  97. console.error(error)
  98. process.exit(1)
  99. }