Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit 2c50f78

Browse files
committed
Adding Luis Json translation
1 parent 596b226 commit 2c50f78

File tree

6 files changed

+213
-27
lines changed

6 files changed

+213
-27
lines changed

packages/luis/README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ USAGE
3030
<!-- commands -->
3131
* [`bf luis:convert`](#bf-luisconvert)
3232
* [`bf luis:translate`](#bf-luistranslate)
33-
* [`bf qnamaker:convert [FILE]`](#bf-qnamakerconvert-file)
34-
* [`bf qnamker:convert`](#bf-qnamkerconvert)
33+
* [`bf qnamaker:convert`](#bf-qnamakerconvert)
34+
* [`bf qnamaker:translate [FILE]`](#bf-qnamakertranslate-file)
3535

3636
## `bf luis:convert`
3737

@@ -77,39 +77,39 @@ OPTIONS
7777

7878
_See code: [src/commands/luis/translate.ts](https://github.com/microsoft/botframework-cli/blob/v1.0.0/src/commands/luis/translate.ts)_
7979

80-
## `bf qnamaker:convert [FILE]`
80+
## `bf qnamaker:convert`
8181

82-
describe the command here
82+
Convert .lu file(s) to a QnA application JSON model or vice versa
8383

8484
```
8585
USAGE
86-
$ bf qnamaker:convert [FILE]
86+
$ bf qnamaker:convert
8787
8888
OPTIONS
89-
-f, --force
90-
-h, --help show CLI help
91-
-n, --name=name name to print
89+
--alterations Indicates if files is QnA Alterations
90+
--in=in (required) Source .qna file(s) or QnA KB JSON file
91+
--log Enables log messages
92+
--name=name Name of the QnA KB
93+
--out=out Output file or folder name. If not specified stdout will be used as output
94+
--recurse Indicates if sub-folders need to be considered to file .qna file(s)
95+
--sort When set, questions collections are alphabetically sorted are alphabetically sorted in .lu files
9296
```
9397

9498
_See code: [src/commands/qnamaker/convert.ts](https://github.com/microsoft/botframework-cli/blob/v1.0.0/src/commands/qnamaker/convert.ts)_
9599

96-
## `bf qnamker:convert`
100+
## `bf qnamaker:translate [FILE]`
97101

98102
describe the command here
99103

100104
```
101105
USAGE
102-
$ bf qnamker:convert
106+
$ bf qnamaker:translate [FILE]
103107
104108
OPTIONS
105-
--alterations Indicates if files is QnA Alterations
106-
--in=in (required) Source .qna file(s) or QnA KB JSON file
107-
--log Enables log messages
108-
--name=name Name of the QnA KB
109-
--out=out Output file or folder name. If not specified stdout will be used as output
110-
--recurse Indicates if sub-folders need to be considered to file .qna file(s)
111-
--sort When set, questions collections are alphabetically sorted are alphabetically sorted in .lu files
109+
-f, --force
110+
-h, --help show CLI help
111+
-n, --name=name name to print
112112
```
113113

114-
_See code: [src/commands/qnamker/convert.ts](https://github.com/microsoft/botframework-cli/blob/v1.0.0/src/commands/qnamker/convert.ts)_
114+
_See code: [src/commands/qnamaker/translate.ts](https://github.com/microsoft/botframework-cli/blob/v1.0.0/src/commands/qnamaker/translate.ts)_
115115
<!-- commandsstop -->

packages/luis/parser/translator/lutranslate.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,28 @@ const retCode = require('./../lufile/enums/CLI-errors')
66
const translateHelpers = require('./../lufile/translate-helpers')
77

88
module.exports = {
9-
translateLu: async function(files, translate_key, to_lang, src_lang, translate_comments, translate_link_text) {
9+
translateLuFile: async function(files, translate_key, to_lang, src_lang, translate_comments, translate_link_text) {
1010
let translation = {}
1111
let i = 0
1212
while(files.length > i) {
1313
let file = files[i++] + ''
1414
try {
15-
translation[path.basename(file)] = await parseFile(file, translate_key, to_lang, src_lang, translate_comments, translate_link_text)
15+
let luObject = await parseFile(file)
16+
translation[path.basename(file)] = await translateLuObject(luObject, translate_key, to_lang, src_lang, translate_comments, translate_link_text)
1617
} catch (err) {
1718
throw(err);
1819
}
1920
}
2021
return translation
22+
},
23+
translateLuObject: async function(luObject, translate_key, to_lang, src_lang, translate_comments, translate_link_text) {
24+
let translation = {}
25+
try {
26+
translation['luTranslation'] = await translateLuObject(luObject, translate_key, to_lang, src_lang, translate_comments, translate_link_text)
27+
} catch (err) {
28+
throw(err);
29+
}
30+
return translation
2131
}
2232
}
2333

@@ -32,26 +42,29 @@ module.exports = {
3242
* @returns {void} nothing
3343
* @throws {exception} Throws on errors. exception object includes errCode and text.
3444
*/
35-
async function parseFile(file, translate_key, to_lang, src_lang, translate_comments, translate_link_text) {
45+
async function parseFile(file) {
3646
if(!fs.existsSync(path.resolve(file))) {
3747
throw(new exception(retCode.errorCode.FILE_OPEN_ERROR, 'Sorry unable to open [' + file + ']'));
3848
}
3949
let fileContent = txtfile.readSync(file);
4050
if (!fileContent) {
4151
throw(new exception(retCode.errorCode.FILE_OPEN_ERROR, 'Sorry, error reading file:' + file));
4252
}
53+
return fileContent
54+
}
4355

56+
async function translateLuObject(luObject, translate_key, to_lang, src_lang, translate_comments, translate_link_text) {
4457
let parsedLocContent = ''
4558
let result = {}
4659
// Support multi-language specification for targets.
4760
// Accepted formats are space or comma separated list of target language codes.
4861
// Tokenize to_lang
49-
let toLang = to_lang.split(/[, ]/g);
62+
let toLang = to_lang.split(/[, ]/g)
5063
for (idx in toLang) {
5164
let tgt_lang = toLang[idx].trim();
5265
if (tgt_lang === '') continue;
5366
try {
54-
parsedLocContent = await translateHelpers.parseAndTranslate(fileContent, translate_key, tgt_lang, src_lang, translate_comments, translate_link_text, false, undefined)
67+
parsedLocContent = await translateHelpers.parseAndTranslate(luObject, translate_key, tgt_lang, src_lang, translate_comments, translate_link_text, false, undefined)
5568
} catch (err) {
5669
throw(err);
5770
}

packages/luis/src/commands/luis/translate.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const fs = require('fs-extra')
33
const path = require('path')
44
const helpers = require('./../../../parser/lufile/helpers')
55
const luTranslator = require('./../../../parser/translator/lutranslate')
6+
const luisConverter = require('./../../../parser/converters/luistoluconverter')
7+
const luConverter = require('./../../../parser/converters/lutoluisconverter')
68

79
export default class LuisTranslate extends Command {
810
static description = ' Translate given LUIS application JSON model or lu file(s)'
@@ -24,19 +26,25 @@ export default class LuisTranslate extends Command {
2426
try {
2527
const {flags} = this.parse(LuisTranslate)
2628
this.inputStat = await fs.stat(flags.in)
27-
let outputStat = await fs.stat(flags.out)
29+
let outputStat = flags.out ? await fs.stat(flags.out) : undefined
2830

29-
if (!this.inputStat.isFile() && outputStat.isFile()) {
31+
if (!this.inputStat.isFile() && outputStat && outputStat.isFile()) {
3032
throw new CLIError('Specified output cannot be applied to input')
3133
}
3234

3335
let isLu = !this.inputStat.isFile() ? true : path.extname(flags.in) === '.lu'
34-
3536
let result: any
3637
let luFiles: any
3738
if (isLu) {
3839
luFiles = await this.getLuFiles(flags.in, flags.recurse)
39-
result = await luTranslator.translateLu(luFiles, flags.translatekey, flags.tgtlang, flags.srclang, flags.translate_comments, flags.translate_link_text)
40+
result = await luTranslator.translateLuFile(luFiles, flags.translatekey, flags.tgtlang, flags.srclang, flags.translate_comments, flags.translate_link_text)
41+
} else {
42+
result = await luisConverter.parseLuisFileToLu(flags.in, false)
43+
result = await luTranslator.translateLuObject(result, flags.translatekey, flags.tgtlang, flags.srclang, flags.translate_comments, flags.translate_link_text)
44+
let tempFile = path.join(__dirname, 'tempfile')
45+
await fs.writeFile(tempFile, result['luTranslation'][flags.tgtlang])
46+
result = await luConverter.parseLuToLuis([tempFile], false, undefined)
47+
await fs.remove(tempFile)
4048
}
4149

4250
if (flags.out) {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import {CLIError, Command, flags} from '@microsoft/bf-cli-command'
2+
const fs = require('fs-extra')
3+
const path = require('path')
4+
const helpers = require('./../../../parser/lufile/helpers')
5+
const luTranslator = require('./../../../parser/translator/lutranslate')
6+
7+
export default class QnamakerTranslate extends Command {
8+
static description = 'Translate given LUIS application JSON model or lu file(s)'
9+
10+
static flags: flags.Input<any> = {
11+
in: flags.string({description: 'Source .lu file(s) or LUIS application JSON model', required: true}),
12+
recurse: flags.boolean({description: 'Indicates if sub-folders need to be considered to file .lu file(s)'}),
13+
out: flags.string({description: 'Output file or folder name. If not specified stdout will be used as output'}),
14+
srclang: flags.string({description: 'Source lang code. Auto detect if missing.'}),
15+
tgtlang: flags.string({description: 'Comma separated list of target languages.', required: true}),
16+
translatekey: flags.string({description: 'Machine translation endpoint key.', required: true}),
17+
translate_comments: flags.string({description: 'When set, machine translate comments found in .lu or .qna file'}),
18+
translate_link_text: flags.string({description: 'When set, machine translate link description in .lu or .qna file'}),
19+
}
20+
21+
inputStat: any
22+
23+
async run() {
24+
try {
25+
const {flags} = this.parse(QnamakerTranslate)
26+
this.inputStat = await fs.stat(flags.in)
27+
let outputStat = await fs.stat(flags.out)
28+
29+
if (!this.inputStat.isFile() && outputStat.isFile()) {
30+
throw new CLIError('Specified output cannot be applied to input')
31+
}
32+
33+
let isLu = !this.inputStat.isFile() ? true : path.extname(flags.in) === '.lu'
34+
35+
let result: any
36+
let luFiles: any
37+
if (isLu) {
38+
luFiles = await this.getLuFiles(flags.in, flags.recurse)
39+
result = await luTranslator.translateLuFile(luFiles, flags.translatekey, flags.tgtlang, flags.srclang, flags.translate_comments, flags.translate_link_text)
40+
}
41+
42+
if (flags.out) {
43+
let languages = flags.tgtlang.split(',')
44+
await this.writeOutput(result, luFiles, isLu, languages, flags.out)
45+
} else {
46+
this.log(result)
47+
}
48+
49+
} catch (err) {
50+
throw err
51+
}
52+
}
53+
54+
private async getLuFiles(input: string | undefined, recurse = false): Promise<Array<any>> {
55+
let filesToParse = []
56+
57+
if (this.inputStat.isFile()) {
58+
filesToParse.push(input)
59+
return filesToParse
60+
}
61+
62+
if (!this.inputStat.isDirectory()) {
63+
throw new CLIError('Sorry, ' + input + ' is not a folder or does not exist')
64+
}
65+
66+
filesToParse = helpers.findLUFiles(input, recurse)
67+
68+
if (filesToParse.length === 0) {
69+
throw new CLIError('Sorry, no .lu files found in the specified folder.')
70+
}
71+
return filesToParse
72+
}
73+
74+
75+
private async writeOutput(translatedObject: any, files: Array<string>, isLu: boolean, languages: Array<string>, out: string) {
76+
let filePath = ''
77+
try {
78+
if (isLu) {
79+
let fileIndex = 0
80+
while (files.length > fileIndex) {
81+
let file = files[fileIndex++] + ''
82+
let lngIndex = 0
83+
while (languages.length > lngIndex) {
84+
let lg = languages[lngIndex++] + ''
85+
filePath = await this.generateNewFilePath(path.basename(file), lg, out)
86+
await fs.writeFile(filePath, translatedObject[path.basename(file)][lg], 'utf-8')
87+
}
88+
}
89+
} else {
90+
await fs.writeFile(filePath, translatedObject, 'utf-8')
91+
}
92+
} catch (err) {
93+
throw new CLIError('Unable to write file - ' + filePath + ' Error: ' + err.message)
94+
}
95+
this.log('Successfully wrote translated model to ' + filePath)
96+
}
97+
98+
private async generateNewFilePath(fileName: string, translatedLanguage: string, output: string): Promise<string> {
99+
let outputStat = await fs.stat(output)
100+
if (outputStat.isFile()) {
101+
return path.join(process.cwd(), output)
102+
} else {
103+
let base = path.join(process.cwd(), output, translatedLanguage)
104+
await fs.mkdirp(base)
105+
return path.join(base, fileName)
106+
}
107+
}
108+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {CLIError} from '@microsoft/bf-cli-command'
2+
const fs = require('fs-extra')
3+
const path = require('path')
4+
const helpers = require('./../../parser/lufile/helpers')
5+
6+
export async function getLuFiles(input: string | undefined, recurse = false): Promise<Array<any>> {
7+
let filesToParse = []
8+
let fileStat = fs.stat(input)
9+
if (fileStat.isFile()) {
10+
filesToParse.push(input)
11+
return filesToParse
12+
}
13+
14+
if (!fileStat.isDirectory()) {
15+
throw new CLIError('Sorry, ' + input + ' is not a folder or does not exist')
16+
}
17+
18+
filesToParse = helpers.findLUFiles(input, recurse)
19+
20+
if (filesToParse.length === 0) {
21+
throw new CLIError('Sorry, no .lu files found in the specified folder.')
22+
}
23+
return filesToParse
24+
}
25+
26+
27+
export async function generateNewFilePath(input: string, output: string, extension = ''): Promise<string> {
28+
let outputStat = await fs.stat(output)
29+
if (outputStat.isFile()) {
30+
return path.join(process.cwd(), output)
31+
}
32+
let inputStat = await fs.stat(input)
33+
let base = path.join(process.cwd(), output)
34+
await fs.mkdirp(base)
35+
if (inputStat.isFile()) {
36+
return path.basename(input, path.extname(input)) + extension
37+
}
38+
39+
return path.join(base, 'file'+extension)
40+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {expect, test} from '@oclif/test'
2+
3+
describe('qnamaker:translate', () => {
4+
test
5+
.stdout()
6+
.command(['qnamaker:translate'])
7+
.it('runs hello', ctx => {
8+
expect(ctx.stdout).to.contain('hello world')
9+
})
10+
11+
test
12+
.stdout()
13+
.command(['qnamaker:translate', '--name', 'jeff'])
14+
.it('runs hello --name jeff', ctx => {
15+
expect(ctx.stdout).to.contain('hello jeff')
16+
})
17+
})

0 commit comments

Comments
 (0)