Commit 5b34fa50 authored by Víctor Gallego Fontenla's avatar Víctor Gallego Fontenla 🏉

Added support for spell checking

parent 2ef052ae
#!/usr/bin/env node
const PO = require('pofile')
const util = require('util')
const Chalk = require('chalk')
const Inquirer = require('inquirer')
const Program = require('commander')
const Language = require('langs')
const pkg = require('./package.json')
const NodeHun = require('nodehun')
const NodeHunSentences = require('nodehun-sentences')
const nspell = require('nspell')
const { exec } = require('child_process')
const parser = util.promisify(PO.load)
console.clear = () => process.stdout.write('\033c')
const run = util.promisify(exec)
let currentPrint = null
const print = string => process.stdout.write(string)
const clear = () => process.stdout.write('\033c')
const println = (string = '') => process.stdout.write(`${string}\n`)
const printLoading = string => {
let dots = ''
print(`${string}\r`)
currentPrint = setInterval(() => {
dots = `${dots}.`
print(`${string}${Chalk.blue.bold(dots)}\r`)
}, 500)
}
const stopPrinting = () => clearInterval(currentPrint)
const checkSpelling = async (dictionary, translation) => {
if(dictionary === null)
return [translation]
const getDictionary = util.promisify(dictionary)
const {aff, dic} = await getDictionary()
const dict = new NodeHun(aff, dic)
const checker = util.promisify(NodeHunSentences)
const result = await checker(dict, translation)
const options = result.map(({word, suggestions}) => suggestions.map(suggestion =>translation.replace(word, suggestion))).reduce((x, y) => [...x, ...y], [])
return [...options]
}
async function installDict(language){
clear()
printLoading(`${Chalk.blue.bold('Searching dictionary for language')} ${Chalk.whiteBright.underline.italic(`'${language}'`)}`)
const capitalizedLanguage = `${language.charAt(0).toUpperCase()}${language.toLowerCase().slice(1)}`
const languageDefinition = [
Language.where("name", capitalizedLanguage),
Language.where("local", capitalizedLanguage),
Language.where("1", language.toLowerCase())
].filter(x => x !== undefined)[0]
const languageCode = languageDefinition !== undefined ? languageDefinition['1'] : ''
const packageName = `dictionary-${languageCode}`
const search = await run(`npm search ${packageName}`)
const dictionaries = search.stdout.split('\n').map(line => line.split('|')).map(([name]) => name.trim()).filter(name => name === packageName)
stopPrinting()
if(dictionaries.length === 1){
clear()
printLoading(`${Chalk.blue.bold("Installing dictionary")} ${Chalk.whiteBright.underline.italic(dictionaries[0])}`)
await run(`npm install ${dictionaries[0]}`)
stopPrinting()
clear()
println(`${Chalk.green.bold("Dictionary")} ${Chalk.whiteBright.underline.italic(dictionaries[0])} ${Chalk.green.bold("installed successfully!")}`)
} else if(dictionaries.length === 0){
clear()
println(Chalk.red.bold(`Dictionary for language ${Chalk.underline.italic(language)} not found!`))
} else {
println(Chalk.bgRed.bold.underline("ERROR! More than one dictionary found for the specified language!"))
}
}
async function listInstalledDict(){
clear()
printLoading(Chalk.blue.bold("Searching for installed dictionaires"))
const installed = await run('npm ls --depth=0 --json')
const packages = Object.entries(JSON.parse(installed.stdout).dependencies).map(([name]) => name).filter(name => name.includes("dictionary"))
stopPrinting()
clear()
println(Chalk.green.bold.underline(`Installed dictionaries`))
for(package of packages){
let lang = Language.where("1", package.split('-')[1])
println(Chalk.green.bold(` * ${lang.name}: ${package}`))
}
}
async function uninstallDict(language){
clear()
printLoading(Chalk.blue.bold(`Uninstalling dictionary for language ${language}`))
const capitalizedLanguage = `${language.charAt(0).toUpperCase()}${language.toLowerCase().slice(1)}`
const languageDefinition = [
Language.where("name", capitalizedLanguage),
Language.where("local", capitalizedLanguage),
Language.where("1", language.toLowerCase())
].filter(x => x !== undefined)[0]
const languageCode = languageDefinition !== undefined ? languageDefinition['1'] : ''
async function processFile(file) {
console.clear()
const installed = await run('npm ls --depth=0 --json')
const packages = Object.entries(JSON.parse(installed.stdout).dependencies)
.map(([name]) => name)
.filter(name => name.includes("dictionary"))
.filter(name => name.split('-')[1] === languageCode)
if(packages.length === 0){
stopPrinting()
clear()
println(Chalk.red.bold(`Dictionary for language ${Chalk.whiteBright.underline.italic(language)} not found!`))
} else {
await run(`npm remove ${packages[0]}`)
stopPrinting()
clear()
println(Chalk.green.bold(`Dictionary ${packages[0]} uninstalled successfully!`))
}
}
async function translateFile(file, options) {
const output = options.output === undefined ? file : options.output
clear()
const { language } = await Inquirer.prompt({type: 'input', name: 'language', message: 'Translation language? '})
console.clear()
console.log(Chalk.green.bold(`You are about to translate ${Chalk.keyword('orange').bold.underline(file)} to ${Chalk.keyword('orange').bold.underline(language)}...`))
console.log()
const capitalizedLanguage = `${language.charAt(0).toUpperCase()}${language.toLowerCase().slice(1)}`
const languageDefinition = [
Language.where("name", capitalizedLanguage),
Language.where("local", capitalizedLanguage),
Language.where("1", language.toLowerCase())
].filter(x => x !== undefined)[0]
const languageCode = languageDefinition !== undefined ? languageDefinition['1'] : ''
let dictionary
try{
dictionary = require(`dictionary-${languageCode}`)
} catch(err) {
dictionary = null
}
clear()
println(Chalk.green.bold(`You are about to translate ${Chalk.keyword('orange').bold.underline(file)} to ${Chalk.keyword('orange').bold.underline(language)}...`))
if(dictionary === null){
println()
println(Chalk.green.bold(`You don't have installed a dictionary for ${Chalk.keyword('orange').bold.underline(language)}, so you won't have spell checking.`))
println(Chalk.green.bold(`If you want to install a dictionary you can use the ${Chalk.keyword('orange').underline('install')} subcommand from this package.`))
}
const { proceed } = await Inquirer.prompt({type: 'confirm', name: 'proceed', message: 'Do you want to continue? '})
if(!proceed){
console.clear()
clear()
println(Chalk.green.bold(`Translation cancelled.`))
return
}
......@@ -26,60 +168,104 @@ async function processFile(file) {
for(item of strings){
if(item.msgstr[0].trim() === ""){
console.clear()
clear()
console.log(Chalk.green.bold(`Translating file ${Chalk.keyword('orange').bold.underline(file)}`))
console.log()
console.log()
console.log(`${Chalk.blue.bold.underline('Message ID:')} ${Chalk.whiteBright.bold.italic(item.msgid)}`)
console.log(`${Chalk.blue.bold.underline('Context:')} ${Chalk.whiteBright.bold.italic(item.msgctxt)}`)
console.log()
console.log()
println(`${Chalk.green.bold('Translating file')} ${Chalk.keyword('orange').bold.underline(file)}`)
println()
println()
println(`${Chalk.blue.bold.underline('Message ID:')} ${Chalk.whiteBright.bold.italic(item.msgid)}`)
println(`${Chalk.blue.bold.underline('Context:')} ${Chalk.whiteBright.bold.italic(item.msgctxt)}`)
println()
println()
const { translation } = await Inquirer.prompt({type: 'input', name: 'translation', message: 'Translation:'})
//TODO check spelling
const translations = await checkSpelling(dictionary, translation)
if(translations.length === 1)
item.msgstr[0] = translations[0]
else{
let newTranslation = await Inquirer.prompt({
type: 'list',
message:"Wrong spelling! Suggestions:",
name: 'value',
choices: [
translation,
new Inquirer.Separator(),
...translations,
new Inquirer.Separator(),
{name: "Write a different translation", value: -1}
],
default: 1
})
item.msgstr[0] = translation
if(newTranslation.value === -1)
newTranslation = await Inquirer.prompt({type: 'input', name: 'value', message: 'Translation:'})
newTranslation = newTranslation.value
item.msgstr[0] = newTranslation
console.log(item)
}
console.clear()
clear()
const { option } = await Inquirer.prompt({type: 'rawlist', message:"Continue translation?", name: 'option', choices: [
"1> Continue without saving",
"2> Save and continue",
"3> Save and exit",
"4> Discard changes and exit"
], default: 1})
const { option } = await Inquirer.prompt({type: 'list', message:"Continue translation?", name: 'option', choices: [
{name: "Continue without saving", value: 1},
{name: "Save and continue", value: 2},
{name: "Save and exit", value: 3},
{name: "Discard changes and exit", value: 4}
], default: 0})
switch(option[0]){
case '1':
switch(option){
case 1:
break
case '2':
await content.save(`${file}`)
case 2:
await content.save(output, () => {})
break
case '3':
await content.save(`${file}`)
case 3:
await content.save(output, () => {})
return
case '4':
case 4:
return
}
}
}
console.log(Chalk.green.bold.underline("All translations done!"))
println(Chalk.green.bold.underline("All translations done!"))
const { save } = await Inquirer.prompt({type: 'confirm', name: 'save', message: 'Do you want to save the file before closing the aplication?'})
if(save)
await content.save(`${file}`)
await content.save(output, () => {})
return
}
Program.version('0.1.1')
.usage('[options] <input file>')
.option('-o, --output [file]', 'Output file (.po). If not specified, it will replace the original file on save.')
.parse(process.argv)
Program.version(`${pkg.name} v${pkg.version}`, '-v, --version')
const [inputFile] = Program.args
Program.command('install <language>')
.alias('i')
.description("Install a new dictionary for spell check support")
.action(installDict)
processFile(inputFile)
Program.command('uninstall <language>')
.alias('u')
.description("Uninstall an installed dictionary")
.action(uninstallDict)
Program.command('list')
.alias('l')
.description("List all installed dictionaries")
.action(listInstalledDict)
Program.command('translate <file>')
.alias('t')
.description("Translate a gettext file")
.option('-o, --output [destination]', 'Output path for the translations file. If not specified, the program will overwrite the input file.')
.action(translateFile)
Program.parse(process.argv)
if (process.argv.length === 2 || !["install", "uninstall", "list", "translate", "i", "u", "l", "t"].includes(process.argv[2]) ) {
Program.outputHelp()
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "translator",
"name": "POTrans",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"description": "CLI tool to translate PO files easily",
"keywords": ["translate", "translator", "translation", "localization", "i18n", "gettext", "po", "pot"],
"homepage": "potrans.vjgallego.me",
"author": {
"name": "Víctor José Gallego Fontenla",
"email": "vjgallego@gmail.com",
"url": "vjgallego.me"
},
"author": "",
"license": "ISC",
"license": "MIT",
"main": "index.js",
"bin": "./index.js",
"repository": "github:MorrisGallego/POTrans",
"dependencies": {
"api-npm": "^1.0.0",
"chalk": "^2.3.0",
"commander": "^2.14.1",
"inquirer": "^5.1.0",
"npm": "^5.6.0",
"nspell": "^1.0.5",
"langs": "^2.0.0",
"nodehun": "^2.0.11",
"nodehun-sentences": "^1.0.4",
"pofile": "^1.0.10"
}
},
"engines": {
"node": ">=8.0.0"
},
"preferGlobal": true
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment