ae-biu
Version:
Born For AE, Born To Do
230 lines (206 loc) • 6.64 kB
JavaScript
// @flow
import program from 'commander'
import chalk from 'chalk'
import path from 'path'
import fs from 'fs'
import { promisify } from 'util'
import sortObject from 'deep-sort-object'
import flattern, { unflatten } from 'flat'
import readDirRecursive from './utils/read-dir-recursive'
/**
* Help
*/
program.on('--help', () => {
console.log()
console.log(' Examples:')
console.log()
console.log(chalk.yellow(' # Get I18N data for all languages you defined in package.json'))
console.log()
console.log(chalk.blue(' $ ae i18n'))
console.log()
console.log(chalk.yellow(' # Get I18N data for specific language(s)'))
console.log()
console.log(chalk.blue(' $ ae i18n --lang zh-HK[,en-US]'))
console.log()
console.log(chalk.blue(' Tips: the first language you specific will be treated as default language'))
})
/**
* Usage
*/
program
.usage('[--lang]')
.option('-l, --lang [language]', 'Get I18N data for specific language')
.option('-u, --no-flat', 'Unflatten I18N data')
.parse(process.argv)
// should flattern I18N data
const flat = program.flat
const handleError = (error: Error): void => {
console.log()
console.error(error)
process.exit(1)
}
const handleFile = async (file: string): Promise<{[x: string]: string}> => {
const temp = {}
const contents = await promisify(fs.readFile)(file, 'utf8')
// if you chose regexp to solve this problem
// you have two problems now
// find i18n expression
const matched = contents.match(/__\((('|")(.(?!__\(('|")))+)\){1}/gm)
if (matched) {
matched.map(item => {
// find i18n message key
const key = item.match(/('.+?')|(".+?")/)[0].replace(/'|"/gm, '')
// find i18n default message
const msgMatched = item.match(/,\s?('|")(.+)('|")\)/)
let defaultMsg
if (msgMatched) {
defaultMsg = msgMatched[2]
} else {
defaultMsg = key
}
temp[key] = defaultMsg
})
}
return temp
}
const run = async (): Promise<any> => {
console.log()
console.log(chalk.yellow('Let\'s start, sing L\'Internationale ~'))
const start = Date.now()
const pkgPath = path.resolve(process.cwd(), 'package.json')
const srcDir = path.resolve(process.cwd(), 'src/modules')
const staticDir = path.resolve(process.cwd(), 'src/static')
try {
await promisify(fs.access)(pkgPath)
} catch (error) {
console.error(chalk.red('Cannot find package.json, please check'))
process.exit(0)
}
const lang = program.lang
let languages = (lang && lang.split(',')) || []
languages = languages.map(lang => lang.trim())
if (!languages.length) {
try {
const pkgContent = await promisify(fs.readFile)(pkgPath)
const pkg = JSON.parse(pkgContent)
const { ae = {} } = pkg
languages = ae.languages
} catch (error) {
handleError(error)
}
}
console.log()
console.log(chalk.yellow(`Your applied languages: ${chalk.red(languages.join(' '))}`))
try {
await promisify(fs.access)(srcDir)
} catch (error) {
console.log()
console.log(chalk.red('Cannot find source directory, do you under an AE project?'))
handleError(error)
}
const files = await readDirRecursive(srcDir, '*.(jsx|js)')
// find modules
const modules = []
files.map(file => {
if (/index\.jsx?$/.test(file)) {
const moduleName = file.split('index')[0]
modules.push(moduleName)
}
})
// sort by moduleName's length
modules.sort((a, b) => b.length - a.length)
// find i18n data
const i18nData = {}
await Promise.all(files.map(async (file) => {
const data = await handleFile(file)
i18nData[file] = data
}))
const handledFiles = new Set()
const moduleData = {}
// merge i18n data by module
await Promise.all(modules.map(async module => {
moduleData[module] = {}
Object.keys(i18nData).map(file => {
if (file.includes(module) && !handledFiles.has(file)) {
moduleData[module] = {
...moduleData[module],
...i18nData[file]
}
handledFiles.add(file)
}
})
const baseLang = languages[0]
let baseData = {}
const i18nFile = path.resolve(module, `${baseLang}.json`)
try {
const fileContent = await promisify(fs.readFile)(i18nFile, 'utf8')
baseData = JSON.parse(fileContent)
} catch (error) {
// not handled, just create a new file
}
// ensure current cover new
baseData = {
...moduleData[module],
...baseData
}
// copy from base data, ensure some default data, such as error code can be added into new languages
await Promise.all(languages.map(async (lang) => {
let data = {}
const i18nFile = path.resolve(module, `${lang}.json`)
try {
const fileContent = await promisify(fs.readFile)(i18nFile, 'utf8')
data = JSON.parse(fileContent)
} catch (error) {
// not handled, just create a new file
}
// ensure current cover new
data = {
...baseData,
...data
}
if (Object.keys(data).length) {
const dataHandler = flat ? flattern : unflatten
let content = JSON.stringify(dataHandler(sortObject(data)), null, 2)
content += '\n' // add new line
await promisify(fs.writeFile)(i18nFile, content)
}
}))
}))
const defaultModules = ['comp', 'fish', 'theme']
await Promise.all(defaultModules.map(async module => {
const baseLang = languages[0]
let baseData = {}
try {
const fileContent = await promisify(fs.readFile)(path.resolve(staticDir, 'locale', module, `${baseLang}.json`), 'utf8')
baseData = JSON.parse(fileContent)
} catch (error) {
console.log()
console.log(chalk.cyan('There is no default file in src/static, please check'))
}
await Promise.all(languages.map(async lang => {
const file = path.resolve(staticDir, 'locale', module, `${lang}.json`)
let data = {}
try {
const fileContent = await promisify(fs.readFile)(file, 'utf8')
data = JSON.parse(fileContent)
} catch (error) {
// not handle, just create a new file
}
data = {
...baseData,
...data
}
if (Object.keys(data).length) {
const dataHandler = flat ? flattern : unflatten
let content = JSON.stringify(dataHandler(sortObject(data)), null, 2)
content += '\n' // add new line
await promisify(fs.writeFile)(file, content)
}
}))
}))
const time = (Date.now() - start)
console.log()
console.log(chalk.yellow(`Done in ${chalk.blue(time + 'ms')} !!!`))
}
run()