const debug = require('debug')('vue-cli-plugin-i18n')
|
const fs = require('fs')
|
const path = require('path')
|
const deepmerge = require('deepmerge')
|
const flatten = require('flat')
|
const unflatten = require('flat').unflatten
|
const {
|
isObject,
|
readEnv,
|
buildEnvContent,
|
writeFile,
|
sortObject
|
} = require('./utils')
|
const i18n = require('./i18n')
|
|
function getValuesFromPath(path, messages) {
|
const paths = path.split('.')
|
let p = null
|
let target = messages
|
while ((p = paths.shift())) {
|
target = target[p]
|
if (!(isObject(target) || Array.isArray(target))) {
|
break
|
}
|
}
|
debug('getValuesFromPath', path, target)
|
return target
|
}
|
|
function assignValuesWithPath(path, value, messages) {
|
const paths = path.split('.')
|
let p = null
|
let target = messages
|
while ((p = paths.shift())) {
|
if (paths.length > 0) {
|
target = target[p]
|
if (target === undefined) {
|
target[p] = target = {}
|
}
|
} else {
|
target[p] = value
|
}
|
}
|
debug('assignValuesToPath', path, value, messages)
|
}
|
|
function getLocales(targetPath) {
|
debug('getLocales', targetPath)
|
return fs
|
.readdirSync(targetPath)
|
.filter(locale => {
|
const stat = fs.statSync(path.join(targetPath, locale))
|
return stat.isFile() && path.extname(locale) === '.json'
|
})
|
.map(locale => {
|
return path.basename(locale, '.json')
|
})
|
}
|
|
function getLocaleMessages(targetPath, locales) {
|
debug('getLocaleMessages', targetPath, locales)
|
return locales.reduce((val, locale) => {
|
const fullPath = `${targetPath}/${locale}.json`
|
val[locale] = require(fullPath)
|
delete require.cache[require.resolve(fullPath)]
|
return val
|
}, {})
|
}
|
|
function getLocalePaths(messages) {
|
debug('getLocalePaths', messages)
|
return Object.keys(messages).reduce((paths, locale) => {
|
paths[locale] = Object.keys(flatten(messages[locale]))
|
return paths
|
}, {})
|
}
|
|
function writeLocaleMessages(targetPath, locale, messages, order) {
|
debug('writeLocaleMessages', targetPath, locale, messages, order)
|
const sortedMessages = sortObject(messages, order)
|
debug('writeLocaleMessages after:', sortedMessages)
|
return fs.writeFileSync(
|
`${targetPath}/${locale}.json`,
|
JSON.stringify(sortedMessages, null, 2),
|
{ encoding: 'utf8' }
|
)
|
}
|
|
module.exports = api => {
|
const { getSharedData, setSharedData, watchSharedData } = api.namespace(
|
'org.kazupon.vue-i18n.'
|
)
|
let clientLocale = 'en'
|
|
function setupAddon(path, options) {
|
debug(`setupAddon: path -> ${path}, options -> ${options}`)
|
const localeDir = options.localeDir
|
setSharedData('order', 'asc')
|
const env = readEnv(`${path}/.env`)
|
const current = env['VUE_APP_I18N_LOCALE'] || 'en'
|
const defaultLocale = env['VUE_APP_I18N_FALLBACK_LOCALE'] || 'en'
|
setSharedData('current', defaultLocale)
|
setSharedData('defaultLocale', defaultLocale)
|
debug(
|
`setupAddon: current -> ${current}, defaultLocale -> ${defaultLocale}`
|
)
|
const locales = getLocales(`${path}/src/${localeDir}`)
|
setSharedData('locales', locales)
|
const messages = getLocaleMessages(`${path}/src/${localeDir}`, locales)
|
setSharedData('localeMessages', messages)
|
setSharedData('localePaths', getLocalePaths(messages))
|
}
|
|
function setupProject(project, eventName) {
|
const rawConfig = require(`${project.path}/vue.config`)
|
const config =
|
rawConfig.pluginOptions && rawConfig.pluginOptions.i18n
|
? rawConfig.pluginOptions.i18n
|
: { localeDir: 'locales' }
|
if (!config.localeDir) {
|
config.localeDir = 'locales'
|
}
|
debug(`${eventName}: load vue.config`, config)
|
setupAddon(project.path, config)
|
return config
|
}
|
|
try {
|
let currentProject = null
|
let currentConfig = null
|
|
api.onProjectOpen((project, previousProject) => {
|
debug('onProjectOpen', project, previousProject)
|
currentConfig = setupProject(project, 'onProjectOpen')
|
currentProject = project
|
})
|
|
api.onPluginReload(project => {
|
debug('onPluginReload', project)
|
currentConfig = setupProject(project, 'onPluginReload')
|
currentProject = project
|
})
|
|
api.onAction('add-path-action', ({ path, locale }) => {
|
debug('add-path-action onAction', path, locale)
|
if (!currentProject || !currentConfig || !path) {
|
console.error('add-path-action: invalid pre-condition !!')
|
return
|
}
|
|
const localePath = `${currentProject.path}/src/${currentConfig.localeDir}`
|
const orderData = getSharedData('order')
|
const locales = getLocales(localePath)
|
const messages = getLocaleMessages(localePath, locales)
|
|
locales.forEach(locale => {
|
const additional = {}
|
additional[path] = '' // set default
|
const original = messages[locale]
|
debug('original', original)
|
debug('additional', additional)
|
const message = deepmerge(original, unflatten(additional))
|
debug('merged', message)
|
writeLocaleMessages(localePath, locale, message, orderData.value)
|
messages[locale] = message
|
})
|
|
setSharedData('localeMessages', messages)
|
setSharedData('localePaths', getLocalePaths(messages))
|
})
|
|
api.onAction('update-path-action', ({ path, old, locale }) => {
|
debug('update-path-action onAction', path, old, locale)
|
if (!currentProject || !currentConfig || !path || !old) {
|
console.error('update-path-action: invalid pre-condition !!')
|
return
|
}
|
|
const localePath = `${currentProject.path}/src/${currentConfig.localeDir}`
|
const locales = getLocales(localePath)
|
const messages = getLocaleMessages(localePath, locales)
|
const orderData = getSharedData('order')
|
|
const original = messages[locale]
|
const oldValues = getValuesFromPath(old, original)
|
const flattendOriginal = flatten(original)
|
delete flattendOriginal[old]
|
const newMessage = unflatten(flattendOriginal)
|
assignValuesWithPath(path, oldValues, newMessage)
|
writeLocaleMessages(localePath, locale, newMessage, orderData.value)
|
messages[locale] = newMessage
|
setSharedData('localeMessages', messages)
|
setSharedData('localePaths', getLocalePaths(messages))
|
})
|
|
api.onAction('delete-path-action', ({ path, locale }) => {
|
debug('delete-path-action onAction', path, locale)
|
if (!currentProject || !currentConfig || !path) {
|
console.log('delete-path-action: invalid pre-condition')
|
return
|
}
|
|
const localePath = `${currentProject.path}/src/${currentConfig.localeDir}`
|
const locales = getLocales(localePath)
|
const messages = getLocaleMessages(localePath, locales)
|
const orderData = getSharedData('order')
|
|
const message = messages[locale]
|
const flattendMessage = flatten(message)
|
delete flattendMessage[path]
|
messages[locale] = unflatten(flattendMessage)
|
const ret = writeLocaleMessages(
|
localePath,
|
locale,
|
messages[locale],
|
orderData.value
|
)
|
debug('write data', ret)
|
|
setSharedData('localeMessages', messages)
|
setSharedData('localePaths', getLocalePaths(messages))
|
})
|
|
api.onAction('edit-message-action', ({ path, value, locale }) => {
|
debug('edit-message-action onAction', path, value, locale)
|
if (!currentProject || !currentConfig || !path) {
|
console.error('edit-message-action: invalid pre-condition !!')
|
return
|
}
|
|
const localePath = `${currentProject.path}/src/${currentConfig.localeDir}`
|
const locales = getLocales(localePath)
|
const localeMessages = getLocaleMessages(localePath, locales)
|
const orderData = getSharedData('order')
|
|
const messages = localeMessages[locale]
|
const flattendMessage = flatten(messages)
|
flattendMessage[path] = value
|
localeMessages[locale] = unflatten(flattendMessage)
|
writeLocaleMessages(
|
localePath,
|
locale,
|
localeMessages[locale],
|
orderData.value
|
)
|
|
setSharedData('localeMessages', localeMessages)
|
setSharedData('localePaths', getLocalePaths(localeMessages))
|
})
|
|
api.onAction('add-locale-action', ({ locale }) => {
|
debug('add-locale-action onAction', locale)
|
if (!currentProject || !currentConfig || !locale) {
|
console.error('add-locale-action: invalid pre-condition')
|
return
|
}
|
|
const localePath = `${currentProject.path}/src/${currentConfig.localeDir}`
|
const defaultLocaleData = getSharedData('defaultLocale')
|
const defaultLocale = defaultLocaleData.value
|
const oldMessages = getLocaleMessages(localePath, getLocales(localePath))
|
const orderData = getSharedData('order')
|
|
const oldFlattendMessages = flatten(oldMessages[defaultLocale])
|
const newLocaleMessages = Object.keys(oldFlattendMessages).reduce(
|
(val, p) => {
|
val[p] = oldFlattendMessages[p]
|
return val
|
},
|
{}
|
)
|
writeLocaleMessages(
|
localePath,
|
locale,
|
unflatten(newLocaleMessages),
|
orderData.value
|
)
|
|
const locales = getLocales(localePath)
|
const messages = getLocaleMessages(localePath, locales)
|
setSharedData('localeMessages', messages)
|
setSharedData('localePaths', getLocalePaths(messages))
|
setSharedData('locales', locales)
|
setSharedData('current', locale)
|
|
debug('add-locale-action: clientLocale', clientLocale)
|
api.notify({
|
title: i18n.t('org.kazupon.vue-i18n.notification.title', clientLocale),
|
message: i18n.t(
|
'org.kazupon.vue-i18n.notification.message',
|
clientLocale,
|
{ locale }
|
),
|
icon: 'done'
|
})
|
})
|
|
api.describeConfig({
|
id: 'org.kazupon.vue-i18n.config',
|
name: 'Vue I18n',
|
description: 'org.kazupon.vue-i18n.config.description',
|
icon: '/_plugin/vue-cli-plugin-i18n/nav-logo.svg',
|
onRead: ({ data, cwd }) => {
|
const env = readEnv(`${cwd}/.env`)
|
const currentLocale = env['VUE_APP_I18N_LOCALE']
|
const defaultLocale = env['VUE_APP_I18N_FALLBACK_LOCALE']
|
debug('onRead', data, clientLocale, currentLocale, defaultLocale)
|
return {
|
prompts: [
|
{
|
type: 'input',
|
name: 'locale',
|
message: 'org.kazupon.vue-i18n.config.prompts.locale.message',
|
description:
|
'org.kazupon.vue-i18n.config.prompts.locale.description',
|
value: currentLocale || 'en'
|
},
|
{
|
type: 'input',
|
name: 'fallbackLocale',
|
message:
|
'org.kazupon.vue-i18n.config.prompts.fallbackLocale.message',
|
description:
|
'org.kazupon.vue-i18n.config.prompts.fallbackLocale.description',
|
value: defaultLocale || 'en'
|
}
|
]
|
}
|
},
|
onWrite: ({ answers, data, cwd }) => {
|
debug('onWrite', cwd, answers, data)
|
const envPath = `${cwd}/.env`
|
const env = readEnv(envPath)
|
const { locale, fallbackLocale } = answers
|
env['VUE_APP_I18N_LOCALE'] = locale
|
env['VUE_APP_I18N_FALLBACK_LOCALE'] = fallbackLocale
|
if (!writeFile(envPath, buildEnvContent(env))) {
|
console.error('onWrite: failed env variables')
|
}
|
}
|
})
|
|
api.describeTask({
|
match: /vue-cli-service i18n:report/,
|
description: 'org.kazupon.vue-i18n.task.report.description',
|
icon: '/_plugin/vue-cli-plugin-i18n/nav-logo.svg',
|
prompts: [
|
{
|
name: 'type',
|
description: 'org.kazupon.vue-i18n.task.report.prompts.type',
|
type: 'list',
|
default: 'missing & unused',
|
choices: [
|
{
|
name: 'missing & unused',
|
value: 'missing & unused'
|
},
|
{
|
name: 'missing',
|
value: 'missing'
|
},
|
{
|
name: 'unused',
|
value: 'unused'
|
}
|
]
|
},
|
{
|
name: 'output',
|
description: 'org.kazupon.vue-i18n.task.report.prompts.output',
|
type: 'input'
|
}
|
],
|
onBeforeRun: async ({ answers, args }) => {
|
if (answers.type && answers.type !== 'missing & unused') {
|
args.push('--type', answers.type)
|
}
|
if (answers.output) {
|
args.push('--output', answers.output)
|
}
|
}
|
})
|
|
api.addView({
|
id: 'org.kazupon.vue-i18n.views.entry',
|
name: 'org.kazupon.vue-i18n.routes.entry',
|
// icon: 'pets',
|
icon: '/_plugin/vue-cli-plugin-i18n/nav-logo.svg',
|
tooltip: 'org.kazupon.vue-i18n.tooltip'
|
})
|
|
const clientAddonOptions = { id: 'org.kazupon.vue-i18n.client-addon' }
|
if (!process.env.VUE_I18N_UI_DEV) {
|
clientAddonOptions['path'] = path.resolve(
|
__dirname,
|
'./client-addon-dist'
|
)
|
} else {
|
clientAddonOptions['url'] = 'http://localhost:8043/index.js'
|
}
|
debug('clientAddonOptions', clientAddonOptions)
|
|
api.addClientAddon(clientAddonOptions)
|
} catch (e) {
|
console.error('unexpted error', e.message)
|
}
|
|
watchSharedData('current', (val, old) => {
|
debug('watch `current`:', val, old)
|
})
|
|
watchSharedData('clientLocale', (val, old) => {
|
debug('watch `clientLocale`:', val, old)
|
clientLocale = val
|
})
|
}
|