index.mjs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. /* eslint-disable no-console, prefer-arrow-callback */
  2. import path from 'node:path'
  3. import fs from 'node:fs'
  4. import { readFile, writeFile } from 'node:fs/promises'
  5. import { fileURLToPath } from 'node:url'
  6. import dedent from 'dedent'
  7. import { remark } from 'remark'
  8. import { headingRange } from 'mdast-util-heading-range'
  9. import remarkFrontmatter from 'remark-frontmatter'
  10. import remarkConfig from '../remark-lint-uppy/index.js'
  11. import { getPaths, sortObjectAlphabetically } from './helpers.mjs'
  12. const { settings: remarkSettings } = remarkConfig
  13. const root = fileURLToPath(new URL('../../', import.meta.url))
  14. const localesPath = path.join(root, 'packages', '@uppy', 'locales')
  15. const templatePath = path.join(localesPath, 'template.js')
  16. const englishLocalePath = path.join(localesPath, 'src', 'en_US.js')
  17. main()
  18. .then(() => {
  19. console.log(`✅ Generated '${englishLocalePath}'`)
  20. console.log('✅ Generated locale docs')
  21. console.log('✅ Generated types')
  22. })
  23. .catch((error) => {
  24. console.error(error)
  25. process.exit(1)
  26. })
  27. function main () {
  28. return getPaths(`${root}/packages/@uppy/**/src/locale.js`)
  29. .then(importFiles)
  30. .then(createCombinedLocale)
  31. .then(({ combinedLocale, locales }) => ({
  32. combinedLocale: sortObjectAlphabetically(combinedLocale),
  33. locales,
  34. }))
  35. .then(({ combinedLocale, locales }) => {
  36. return readFile(templatePath, 'utf-8')
  37. .then((fileString) => populateTemplate(fileString, combinedLocale))
  38. .then((file) => writeFile(englishLocalePath, file))
  39. .then(() => {
  40. for (const [pluginName, locale] of Object.entries(locales)) {
  41. generateLocaleDocs(pluginName)
  42. generateTypes(pluginName, locale)
  43. }
  44. return locales
  45. })
  46. })
  47. }
  48. async function importFiles (paths) {
  49. const locales = {}
  50. for (const filePath of paths) {
  51. const pluginName = path.basename(path.join(filePath, '..', '..'))
  52. // Note: `.default` should be removed when we move to ESM
  53. const locale = (await import(filePath)).default
  54. locales[pluginName] = locale
  55. }
  56. return locales
  57. }
  58. function createCombinedLocale (locales) {
  59. return new Promise((resolve, reject) => {
  60. const combinedLocale = {}
  61. const entries = Object.entries(locales)
  62. for (const [pluginName, locale] of entries) {
  63. Object.entries(locale.strings).forEach(([key, value]) => {
  64. if (key in combinedLocale && value !== combinedLocale[key]) {
  65. reject(new Error(`'${key}' from ${pluginName} already exists in locale pack.`))
  66. }
  67. combinedLocale[key] = value
  68. })
  69. }
  70. resolve({ combinedLocale, locales })
  71. })
  72. }
  73. function populateTemplate (fileString, combinedLocale) {
  74. const formattedLocale = JSON.stringify(combinedLocale, null, ' ')
  75. return fileString.replace('en_US.strings = {}', `en_US.strings = ${formattedLocale}`)
  76. }
  77. function generateTypes (pluginName, locale) {
  78. const allowedStringTypes = Object.keys(locale.strings)
  79. .map((key) => ` | '${key}'`)
  80. .join('\n')
  81. const pluginClassName = pluginName
  82. .split('-')
  83. .map((str) => str.replace(/^\w/, (c) => c.toUpperCase()))
  84. .join('')
  85. const localePath = path.join(
  86. root,
  87. 'packages',
  88. '@uppy',
  89. pluginName,
  90. 'types',
  91. 'generatedLocale.d.ts'
  92. )
  93. const localeTypes = dedent`
  94. /* eslint-disable */
  95. import type { Locale } from '@uppy/core'
  96. type ${pluginClassName}Locale = Locale<
  97. ${allowedStringTypes}
  98. >
  99. export default ${pluginClassName}Locale
  100. `
  101. fs.writeFileSync(localePath, localeTypes)
  102. }
  103. function generateLocaleDocs (pluginName) {
  104. const fileName = `${pluginName}.md`
  105. const docPath = path.join(root, 'website', 'src', 'docs', fileName)
  106. const localePath = path.join(root, 'packages', '@uppy', pluginName, 'src', 'locale.js')
  107. const rangeOptions = { test: 'locale: {}', ignoreFinalDefinitions: true }
  108. if (!fs.existsSync(docPath)) {
  109. console.error(
  110. `⚠️ Could not find markdown documentation file for "${pluginName}". Make sure the plugin name matches the markdown file name.`
  111. )
  112. return
  113. }
  114. remark()
  115. .data('settings', remarkSettings)
  116. .use(remarkFrontmatter)
  117. .use(() => (tree) => {
  118. // Replace all nodes after the locale heading until the next heading (or eof)
  119. headingRange(tree, rangeOptions, (start, _, end) => [
  120. start,
  121. {
  122. type: 'html',
  123. // `module.exports` is not allowed by eslint in our docs.
  124. // The script outputs an extra newline which also isn't excepted by eslint
  125. value: '<!-- eslint-disable no-restricted-globals, no-multiple-empty-lines -->',
  126. },
  127. {
  128. type: 'code',
  129. lang: 'js',
  130. meta: null,
  131. value: fs.readFileSync(localePath, 'utf-8'),
  132. },
  133. end,
  134. ])
  135. })
  136. .process(fs.readFileSync(docPath))
  137. .then((file) => fs.writeFileSync(docPath, String(file)))
  138. }