index.mjs 4.9 KB

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