build: output locale list json file
fix #1041
Can you please export the list of locales available?  This would be a much better approach than having to manually write it out or programmatically use `fs.readdirSync` to get the full list.  It would also be nice to have an option to load all locales (require them all, or a list passed) at once.
Export list of locales available, and a function to require an Array of locales
diff --git a/.gitignore b/.gitignore
index 951f55934..9b99c9ee0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,7 @@ coverage
 /dayjs.min.js
 /esm
 /index.d.ts
+locale.json
 
 #dev
 demo.js
diff --git a/build/index.js b/build/index.js
index 683170df5..4651dd61e 100644
--- a/build/index.js
+++ b/build/index.js
@@ -8,17 +8,34 @@ const { ncp } = require('ncp')
 const { promisify } = util
 
 const promisifyReadDir = promisify(fs.readdir)
+const promisifyReadFile = promisify(fs.readFile)
+const promisifyWriteFile = promisify(fs.writeFile)
 
+const localeNameRegex = /\/\/ (.*) \[/
 const formatName = n => n.replace(/\.js/, '').replace('-', '_')
 
+const localePath = path.join(__dirname, '../src/locale')
+
 async function build(option) {
   const bundle = await rollup.rollup(option.input)
   await bundle.write(option.output)
 }
 
+async function listLocaleJson(localeArr) {
+  const localeListArr = []
+  await Promise.all(localeArr.map(async (l) => {
+    const localeData = await promisifyReadFile(path.join(localePath, l), 'utf-8')
+    localeListArr.push({
+      key: l.slice(0, -3),
+      name: localeData.match(localeNameRegex)[1]
+    })
+  }))
+  promisifyWriteFile(path.join(__dirname, '../locale.json'), JSON.stringify(localeListArr), 'utf8')
+}
+
 (async () => {
   try {
-    const locales = await promisifyReadDir(path.join(__dirname, '../src/locale'))
+    const locales = await promisifyReadDir(localePath)
     locales.forEach((l) => {
       build(configFactory({
         input: `./src/locale/${l}`,
@@ -42,6 +59,9 @@ async function build(option) {
     }))
 
     await promisify(ncp)('./types/', './')
+
+    // list locales
+    await listLocaleJson(locales)
   } catch (e) {
     console.error(e) // eslint-disable-line no-console
   }
diff --git a/src/locale/bi.js b/src/locale/bi.js
index 7f6cbecba..8dbb0a56d 100644
--- a/src/locale/bi.js
+++ b/src/locale/bi.js
@@ -1,3 +1,4 @@
+// Bislama [bi]
 import dayjs from 'dayjs'
 
 const locale = {
diff --git a/src/locale/en.js b/src/locale/en.js
index a9eb16369..71c2f226e 100644
--- a/src/locale/en.js
+++ b/src/locale/en.js
@@ -1,3 +1,4 @@
+// English [en]
 // We don't need weekdaysShort, weekdaysMin, monthsShort in en.js locale
 export default {
   name: 'en',

diff --git a/test/locale/keys.test.js b/test/locale/keys.test.js
index 876d6b338..b3c480e0f 100644
--- a/test/locale/keys.test.js
+++ b/test/locale/keys.test.js
@@ -4,14 +4,17 @@ import dayjs from '../../src'
 
 const localeDir = '../../src/locale'
 const Locale = []
+const localeNameRegex = /\/\/ (.*) \[/
 
 // load all locales from locale dir
 fs.readdirSync(path.join(__dirname, localeDir))
   .forEach((file) => {
+    const fPath = path.join(__dirname, localeDir, file)
     Locale.push({
       name: file,
       // eslint-disable-next-line import/no-dynamic-require, global-require
-      content: require(path.join(__dirname, localeDir, file)).default
+      content: require(fPath).default,
+      file: fs.readFileSync(fPath, 'utf-8')
     })
   })
 
@@ -31,6 +34,9 @@ Locale.forEach((locale) => {
       yearStart,
       meridiem
     } = locale.content
+    // comments required
+    const commentsMatchResult = locale.file.match(localeNameRegex)
+    expect(commentsMatchResult[1]).not.toBeUndefined()
 
     expect(name).toEqual(locale.name.replace('.js', ''))
     expect(weekdays).toEqual(expect.any(Array))

{"test/locale/keys.test.js": {"run": "PASS", "test": "FAIL", "fix": "PASS"}}
{"test/timezone.test.js:Add Time days (DST)": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/get-set.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/utc.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/quarterOfYear.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/objectSupport.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/fi.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/sk.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/sv.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/advancedFormat.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/weekYear.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/toArray.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/timezone.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/isSameOrBefore.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/timezone.test.js:Utc Offset": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/pluralGetSet.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/timezone.test.js:Diff (DST)": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/utc-utcOffset.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/utils.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/relativeTime.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/parse.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/isMoment.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/isYesterday.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/pl.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/toObject.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/localizedFormat.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/updateLocale.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/constructor.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/weekday.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/isSameOrAfter.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/weekOfYear.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/comparison.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/isLeapYear.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/query.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/localeData.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/calendar.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/isTomorrow.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/et.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/zh.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/isToday.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/declarations.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/hr.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/timezone.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/badMutable.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/customParseFormat.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/ru.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/duration.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/isoWeek.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/minMax.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/dayOfYear.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/isoWeeksInYear.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/timezone.test.js:UTC add day in DST": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/uk.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/buddhistEra.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/cs.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/manipulate.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/display.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/lt.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/zh-cn.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/locale/en.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "test/plugin/isBetween.test.js": {"run": "PASS", "test": "PASS", "fix": "PASS"}}
{"test/locale/keys.test.js": {"run": "PASS", "test": "FAIL", "fix": "PASS"}}
{"passed_count": 63, "failed_count": 0, "skipped_count": 0, "passed_tests": ["test/timezone.test.js:Add Time days (DST)", "test/plugin.test.js", "test/get-set.test.js", "test/plugin/utc.test.js", "test/plugin/quarterOfYear.test.js", "test/plugin/objectSupport.test.js", "test/locale/fi.test.js", "test/locale/sk.test.js", "test/locale/sv.test.js", "test/plugin/advancedFormat.test.js", "test/plugin/weekYear.test.js", "test/plugin/toArray.test.js", "test/timezone.test.js", "test/plugin/isSameOrBefore.test.js", "test/timezone.test.js:Utc Offset", "test/plugin/pluralGetSet.test.js", "test/timezone.test.js:Diff (DST)", "test/plugin/utc-utcOffset.test.js", "test/locale/keys.test.js", "test/utils.test.js", "test/plugin/relativeTime.test.js", "test/parse.test.js", "test/plugin/isMoment.test.js", "test/plugin/isYesterday.test.js", "test/locale/pl.test.js", "test/plugin/toObject.test.js", "test/plugin/localizedFormat.test.js", "test/plugin/updateLocale.test.js", "test/constructor.test.js", "test/plugin/weekday.test.js", "test/plugin/isSameOrAfter.test.js", "test/plugin/weekOfYear.test.js", "test/comparison.test.js", "test/plugin/isLeapYear.test.js", "test/query.test.js", "test/plugin/localeData.test.js", "test/plugin/calendar.test.js", "test/plugin/isTomorrow.test.js", "test/locale/et.test.js", "test/locale/zh.test.js", "test/plugin/isToday.test.js", "test/plugin/declarations.test.js", "test/locale/hr.test.js", "test/plugin/timezone.test.js", "test/locale.test.js", "test/plugin/badMutable.test.js", "test/plugin/customParseFormat.test.js", "test/locale/ru.test.js", "test/plugin/duration.test.js", "test/plugin/isoWeek.test.js", "test/plugin/minMax.test.js", "test/plugin/dayOfYear.test.js", "test/plugin/isoWeeksInYear.test.js", "test/timezone.test.js:UTC add day in DST", "test/locale/uk.test.js", "test/plugin/buddhistEra.test.js", "test/locale/cs.test.js", "test/manipulate.test.js", "test/display.test.js", "test/locale/lt.test.js", "test/locale/zh-cn.test.js", "test/locale/en.test.js", "test/plugin/isBetween.test.js"], "failed_tests": [], "skipped_tests": []}
{"passed_count": 62, "failed_count": 1, "skipped_count": 0, "passed_tests": ["test/timezone.test.js:Add Time days (DST)", "test/plugin.test.js", "test/get-set.test.js", "test/plugin/utc.test.js", "test/plugin/quarterOfYear.test.js", "test/plugin/objectSupport.test.js", "test/locale/fi.test.js", "test/locale/sk.test.js", "test/locale/sv.test.js", "test/plugin/advancedFormat.test.js", "test/plugin/weekYear.test.js", "test/plugin/toArray.test.js", "test/timezone.test.js", "test/timezone.test.js:Utc Offset", "test/plugin/isSameOrBefore.test.js", "test/plugin/pluralGetSet.test.js", "test/timezone.test.js:Diff (DST)", "test/plugin/utc-utcOffset.test.js", "test/utils.test.js", "test/plugin/relativeTime.test.js", "test/parse.test.js", "test/plugin/isMoment.test.js", "test/plugin/isYesterday.test.js", "test/locale/pl.test.js", "test/plugin/toObject.test.js", "test/plugin/localizedFormat.test.js", "test/plugin/updateLocale.test.js", "test/constructor.test.js", "test/plugin/weekday.test.js", "test/plugin/isSameOrAfter.test.js", "test/plugin/weekOfYear.test.js", "test/plugin/isLeapYear.test.js", "test/comparison.test.js", "test/query.test.js", "test/plugin/localeData.test.js", "test/plugin/isTomorrow.test.js", "test/plugin/calendar.test.js", "test/locale/et.test.js", "test/locale/zh.test.js", "test/plugin/isToday.test.js", "test/locale/hr.test.js", "test/plugin/declarations.test.js", "test/plugin/timezone.test.js", "test/locale.test.js", "test/plugin/badMutable.test.js", "test/plugin/customParseFormat.test.js", "test/locale/ru.test.js", "test/plugin/duration.test.js", "test/plugin/isoWeek.test.js", "test/plugin/minMax.test.js", "test/plugin/dayOfYear.test.js", "test/plugin/isoWeeksInYear.test.js", "test/timezone.test.js:UTC add day in DST", "test/locale/uk.test.js", "test/manipulate.test.js", "test/locale/cs.test.js", "test/plugin/buddhistEra.test.js", "test/display.test.js", "test/locale/lt.test.js", "test/locale/zh-cn.test.js", "test/locale/en.test.js", "test/plugin/isBetween.test.js"], "failed_tests": ["test/locale/keys.test.js"], "skipped_tests": []}
{"passed_count": 63, "failed_count": 0, "skipped_count": 0, "passed_tests": ["test/timezone.test.js:Add Time days (DST)", "test/plugin.test.js", "test/get-set.test.js", "test/plugin/utc.test.js", "test/plugin/quarterOfYear.test.js", "test/plugin/objectSupport.test.js", "test/locale/fi.test.js", "test/locale/sk.test.js", "test/locale/sv.test.js", "test/plugin/advancedFormat.test.js", "test/plugin/weekYear.test.js", "test/plugin/toArray.test.js", "test/timezone.test.js", "test/timezone.test.js:Utc Offset", "test/plugin/isSameOrBefore.test.js", "test/plugin/pluralGetSet.test.js", "test/timezone.test.js:Diff (DST)", "test/plugin/utc-utcOffset.test.js", "test/locale/keys.test.js", "test/utils.test.js", "test/plugin/relativeTime.test.js", "test/parse.test.js", "test/plugin/isMoment.test.js", "test/plugin/isYesterday.test.js", "test/locale/pl.test.js", "test/plugin/toObject.test.js", "test/plugin/localizedFormat.test.js", "test/plugin/updateLocale.test.js", "test/constructor.test.js", "test/plugin/weekday.test.js", "test/plugin/isSameOrAfter.test.js", "test/plugin/weekOfYear.test.js", "test/plugin/isLeapYear.test.js", "test/comparison.test.js", "test/query.test.js", "test/plugin/localeData.test.js", "test/plugin/calendar.test.js", "test/locale/et.test.js", "test/plugin/isTomorrow.test.js", "test/locale/zh.test.js", "test/plugin/isToday.test.js", "test/locale/hr.test.js", "test/plugin/declarations.test.js", "test/plugin/timezone.test.js", "test/plugin/badMutable.test.js", "test/locale.test.js", "test/plugin/customParseFormat.test.js", "test/locale/ru.test.js", "test/plugin/isoWeek.test.js", "test/plugin/duration.test.js", "test/plugin/dayOfYear.test.js", "test/plugin/minMax.test.js", "test/plugin/isoWeeksInYear.test.js", "test/timezone.test.js:UTC add day in DST", "test/manipulate.test.js", "test/locale/uk.test.js", "test/locale/cs.test.js", "test/display.test.js", "test/plugin/buddhistEra.test.js", "test/locale/lt.test.js", "test/locale/zh-cn.test.js", "test/locale/en.test.js", "test/plugin/isBetween.test.js"], "failed_tests": [], "skipped_tests": []}