auto-lang 1.0.7 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +177 -168
- package/package.json +1 -1
- package/src/index.js +136 -131
- package/src/utils/Logger.mjs +7 -7
- package/src/utils/validation.mjs +37 -37
package/README.md
CHANGED
@@ -1,168 +1,177 @@
|
|
1
|
-
# Auto Lang
|
2
|
-
|
3
|
-
Generate translation files for multiple languages.
|
4
|
-
|
5
|
-
Write once for a single language and automatically get translated json files for others.
|
6
|
-
## Installation
|
7
|
-
### Using npm
|
8
|
-
$ npm install auto-lang
|
9
|
-
### Using yarn
|
10
|
-
$ yarn add auto-lang
|
11
|
-
|
12
|
-
## Usage
|
13
|
-
You could either install the package and add a script to `package.json` or use the `npx` command directly from the terminal.
|
14
|
-
|
15
|
-
### 1. Using the `npx` command
|
16
|
-
$ npx auto-lang [options]
|
17
|
-
|
18
|
-
### 2. Using a script in `package.json`
|
19
|
-
|
20
|
-
|
21
|
-
{
|
22
|
-
"
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
"
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
"
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
}
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
```
|
1
|
+
# Auto Lang
|
2
|
+
|
3
|
+
Generate translation files for multiple languages.
|
4
|
+
|
5
|
+
Write once for a single language and automatically get translated json files for others.
|
6
|
+
## Installation
|
7
|
+
### Using npm
|
8
|
+
$ npm install auto-lang
|
9
|
+
### Using yarn
|
10
|
+
$ yarn add auto-lang
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
You could either install the package and add a script to `package.json` or use the `npx` command directly from the terminal.
|
14
|
+
|
15
|
+
### 1. Using the `npx` command
|
16
|
+
$ npx auto-lang [options]
|
17
|
+
|
18
|
+
### 2. Using a script in `package.json`
|
19
|
+
```json
|
20
|
+
{
|
21
|
+
"scripts": {
|
22
|
+
"gen-lang": "auto-lang [options]"
|
23
|
+
}
|
24
|
+
}
|
25
|
+
```
|
26
|
+
|
27
|
+
Now, in the terminal run:
|
28
|
+
|
29
|
+
$ npm run gen-lang
|
30
|
+
|
31
|
+
Or, using yarn:
|
32
|
+
|
33
|
+
$ yarn gen-lang
|
34
|
+
|
35
|
+
**Note:** You can give your script any name you wish. Also, replace `[options]` with any of the options below.
|
36
|
+
|
37
|
+
#### Options
|
38
|
+
|
39
|
+
-V, --version output the version number
|
40
|
+
-f, --from <lang> language to translate from
|
41
|
+
-t, --to <lang...> languages to translate to (seperated by space)
|
42
|
+
-d, --dir <directory> directory containing the language files (default: "translations")
|
43
|
+
-s, --skip-existing skip existing keys during translation
|
44
|
+
-g, --gen-type <lang> generate types from language file
|
45
|
+
-h, --help display help for command
|
46
|
+
|
47
|
+
**Note:** `<lang>` must be a valid [ISO 639-1 language code](https://localizely.com/iso-639-1-list/).
|
48
|
+
|
49
|
+
### Examples
|
50
|
+
|
51
|
+
These examples assume there's a folder `translations` in the root directory the command is executed.
|
52
|
+
|
53
|
+
You can also pass `--dir <directory>` to change the folder that contains your language `json` files.
|
54
|
+
|
55
|
+
There is a file `en.json` in the translations folder
|
56
|
+
|
57
|
+
```json
|
58
|
+
{
|
59
|
+
"GENERAL": {
|
60
|
+
"OK": "OK",
|
61
|
+
"CANCEL": "Cancel",
|
62
|
+
"ACCEPT": "Accept",
|
63
|
+
"DECLINE": "Decline"
|
64
|
+
},
|
65
|
+
"GREETINGS": {
|
66
|
+
"HELLO": "Hello",
|
67
|
+
"HI": "Hi",
|
68
|
+
"GOOD_MORNING": "Good morning"
|
69
|
+
}
|
70
|
+
}
|
71
|
+
```
|
72
|
+
Get translation files for French (fr) and Spanish (es).
|
73
|
+
|
74
|
+
$ npx auto-lang --from en --to fr es
|
75
|
+
|
76
|
+
Two files have been created; `fr.json` and `es.json` in the `translations` folder.
|
77
|
+
|
78
|
+
+-- root
|
79
|
+
| +-- translations
|
80
|
+
| | +-- en.json
|
81
|
+
| | +-- fr.json
|
82
|
+
| | +-- es.json
|
83
|
+
|
84
|
+
```json
|
85
|
+
/* fr.json */
|
86
|
+
|
87
|
+
{
|
88
|
+
"GENERAL": {
|
89
|
+
"OK": "D'ACCORD",
|
90
|
+
"CANCEL": "Annuler",
|
91
|
+
"ACCEPT": "Accepter",
|
92
|
+
"DECLINE": "Déclin"
|
93
|
+
},
|
94
|
+
"GREETINGS": {
|
95
|
+
"HELLO": "Bonjour",
|
96
|
+
"HI": "Salut",
|
97
|
+
"GOOD_MORNING": "Bonjour"
|
98
|
+
}
|
99
|
+
}
|
100
|
+
```
|
101
|
+
|
102
|
+
```json
|
103
|
+
/* es.json */
|
104
|
+
|
105
|
+
{
|
106
|
+
"GENERAL": {
|
107
|
+
"OK": "OK",
|
108
|
+
"CANCEL": "Cancelar",
|
109
|
+
"ACCEPT": "Aceptar",
|
110
|
+
"DECLINE": "Rechazar"
|
111
|
+
},
|
112
|
+
"GREETINGS": {
|
113
|
+
"HELLO": "Hola",
|
114
|
+
"HI": "Hola",
|
115
|
+
"GOOD_MORNING": "Buenos dias"
|
116
|
+
}
|
117
|
+
}
|
118
|
+
```
|
119
|
+
|
120
|
+
If you use typescript in your project, you can generate a typescript file to use in your code.
|
121
|
+
|
122
|
+
$ npx auto-lang --gen-type en
|
123
|
+
|
124
|
+
This will generate a `GlobalTranslation` type based on the structure of the `translations/en.json` file.
|
125
|
+
|
126
|
+
+-- root
|
127
|
+
| +-- translations
|
128
|
+
| | +-- types
|
129
|
+
| | | +-- index.ts
|
130
|
+
| | +-- en.json
|
131
|
+
| | +-- fr.json
|
132
|
+
| | +-- es.json
|
133
|
+
|
134
|
+
```ts
|
135
|
+
/* translations/types/index.ts */
|
136
|
+
|
137
|
+
type NestedKeyOf<ObjectType extends object> = {
|
138
|
+
[Key in keyof ObjectType & string]: ObjectType[Key] extends object
|
139
|
+
? // @ts-ignore
|
140
|
+
`${Key}.${NestedKeyOf<ObjectType[Key]>}`
|
141
|
+
: `${Key}`;
|
142
|
+
}[keyof ObjectType & string];
|
143
|
+
|
144
|
+
export type GlobalTranslation = NestedKeyOf<GlobalTranslationType>;
|
145
|
+
|
146
|
+
interface GlobalTranslationType {
|
147
|
+
GENERAL: GENERAL;
|
148
|
+
GREETINGS: GREETINGS;
|
149
|
+
}
|
150
|
+
|
151
|
+
interface GREETINGS {
|
152
|
+
HELLO: string;
|
153
|
+
HI: string;
|
154
|
+
GOOD_MORNING: string;
|
155
|
+
}
|
156
|
+
|
157
|
+
interface GENERAL {
|
158
|
+
OK: string;
|
159
|
+
CANCEL: string;
|
160
|
+
ACCEPT: string;
|
161
|
+
DECLINE: string;
|
162
|
+
}
|
163
|
+
|
164
|
+
```
|
165
|
+
|
166
|
+
Now you should be able to use `GlobalTranslation` in your code.
|
167
|
+
|
168
|
+
```ts
|
169
|
+
import { GlobalTranslation } from './translations/types';
|
170
|
+
|
171
|
+
const translate = (key: GlobalTranslation) => {
|
172
|
+
// your code
|
173
|
+
};
|
174
|
+
|
175
|
+
translate('GENERAL.ACCEPT'); // Intellisense and type check from GlobalTranslation
|
176
|
+
|
177
|
+
```
|
package/package.json
CHANGED
package/src/index.js
CHANGED
@@ -1,131 +1,136 @@
|
|
1
|
-
#!/usr/bin/env node
|
2
|
-
|
3
|
-
import path from 'path';
|
4
|
-
import { promises as fs
|
5
|
-
import {
|
6
|
-
import {
|
7
|
-
import
|
8
|
-
import
|
9
|
-
import
|
10
|
-
|
11
|
-
import
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
const
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
.
|
30
|
-
.
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
'
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
)
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
}
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
}
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
import path from 'path';
|
4
|
+
import {existsSync, promises as fs} from 'fs';
|
5
|
+
import {Command} from 'commander';
|
6
|
+
import {createSpinner} from 'nanospinner';
|
7
|
+
import translate from 'translate';
|
8
|
+
import JsonToTS from 'json-to-ts';
|
9
|
+
import prettier from 'prettier';
|
10
|
+
|
11
|
+
import {Logger} from './utils/Logger.mjs';
|
12
|
+
import {validateOptions} from './utils/validation.mjs';
|
13
|
+
|
14
|
+
const APP_VERSION = '1.0.8';
|
15
|
+
|
16
|
+
const program = new Command();
|
17
|
+
const nodeMajVer = parseInt(process.version.substring(1).split('.')[0]);
|
18
|
+
|
19
|
+
if (nodeMajVer < 14) {
|
20
|
+
Logger.error(`Node version >= 14.x.x is required`);
|
21
|
+
|
22
|
+
process.exit(1);
|
23
|
+
}
|
24
|
+
|
25
|
+
program
|
26
|
+
.name('auto-lang')
|
27
|
+
.description('Generate translation files for multiple languages (i18n)')
|
28
|
+
.version(APP_VERSION)
|
29
|
+
.option('-f, --from <lang>', 'language to translate from')
|
30
|
+
.option(
|
31
|
+
'-t, --to <lang...>',
|
32
|
+
'languages to translate to (seperated by space)'
|
33
|
+
)
|
34
|
+
.option(
|
35
|
+
'-d, --dir <directory>',
|
36
|
+
'directory containing the language files',
|
37
|
+
'translations'
|
38
|
+
)
|
39
|
+
.option('-s, --skip-existing', 'skip existing keys during translation')
|
40
|
+
.option('-g, --gen-type <lang>', 'generate types from language file')
|
41
|
+
.parse();
|
42
|
+
|
43
|
+
const { from, to, genType, inputFile, genTypeFile, dir, skipExisting } = validateOptions(
|
44
|
+
program.opts()
|
45
|
+
);
|
46
|
+
|
47
|
+
const inputJson = JSON.parse(
|
48
|
+
await fs.readFile(from ? inputFile : genTypeFile, { encoding: 'utf-8' })
|
49
|
+
);
|
50
|
+
|
51
|
+
async function makeTranslatedCopy(obj1, obj2, options) {
|
52
|
+
for (let [key, value] of Object.entries(obj1)) {
|
53
|
+
if (typeof value === 'object') {
|
54
|
+
obj2[key] = {};
|
55
|
+
await makeTranslatedCopy(value, obj2[key], options);
|
56
|
+
} else {
|
57
|
+
try {
|
58
|
+
if(!(obj2[key] && skipExisting)) obj2[key] = await translate(value, { from, to: options.to });
|
59
|
+
} catch (err) {
|
60
|
+
console.log('\n');
|
61
|
+
Logger.error(err.message);
|
62
|
+
process.exit(1);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
const getTranslation = (language) =>
|
69
|
+
new Promise(async (resolve, reject) => {
|
70
|
+
let translatedObj = {};
|
71
|
+
const outputFile = path.join(process.cwd(), dir, `${language}.json`);
|
72
|
+
|
73
|
+
if(existsSync(outputFile)) {
|
74
|
+
translatedObj = JSON.parse(
|
75
|
+
await fs.readFile(outputFile, { encoding: 'utf-8' })
|
76
|
+
);
|
77
|
+
}
|
78
|
+
|
79
|
+
await makeTranslatedCopy(inputJson, translatedObj, { to: language });
|
80
|
+
|
81
|
+
resolve(JSON.stringify(translatedObj, null, 4));
|
82
|
+
});
|
83
|
+
|
84
|
+
async function createDeclarationFile() {
|
85
|
+
const spinner = createSpinner('Creating language type file').start();
|
86
|
+
|
87
|
+
const interfaces = JsonToTS(inputJson, { rootName: 'GlobalTranslationType' });
|
88
|
+
const typesDir = path.join(process.cwd(), dir, 'types');
|
89
|
+
|
90
|
+
if (!existsSync(typesDir)) {
|
91
|
+
fs.mkdir(typesDir);
|
92
|
+
}
|
93
|
+
|
94
|
+
const declarationFile = path.join(typesDir, 'index.ts');
|
95
|
+
|
96
|
+
const result = `
|
97
|
+
type NestedKeyOf<ObjectType extends object> = {
|
98
|
+
[Key in keyof ObjectType & string]: ObjectType[Key] extends object
|
99
|
+
? // @ts-ignore
|
100
|
+
\`$\{Key}.$\{NestedKeyOf<ObjectType[Key]>}\`
|
101
|
+
: \`$\{Key}\`
|
102
|
+
}[keyof ObjectType & string]
|
103
|
+
|
104
|
+
export type GlobalTranslation = NestedKeyOf<GlobalTranslationType>;
|
105
|
+
|
106
|
+
${interfaces.join('\n\n')}
|
107
|
+
`;
|
108
|
+
|
109
|
+
const formattedResult = prettier.format(result, { parser: 'typescript' });
|
110
|
+
|
111
|
+
fs.writeFile(declarationFile, formattedResult);
|
112
|
+
spinner.success({ text: 'Language type file created' });
|
113
|
+
}
|
114
|
+
|
115
|
+
async function translateFile() {
|
116
|
+
let spinner, langFile, tranlatedJson;
|
117
|
+
|
118
|
+
for (let lang of to) {
|
119
|
+
langFile = path.join(process.cwd(), dir, `${lang}.json`);
|
120
|
+
spinner = createSpinner(`Translating to ${lang}...`).start();
|
121
|
+
|
122
|
+
tranlatedJson = await getTranslation(lang);
|
123
|
+
// await sleep(1000);
|
124
|
+
await fs.writeFile(langFile, tranlatedJson);
|
125
|
+
|
126
|
+
spinner.success({ text: `Complete` });
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
if (from && to) {
|
131
|
+
await translateFile();
|
132
|
+
}
|
133
|
+
|
134
|
+
if (genType) {
|
135
|
+
await createDeclarationFile();
|
136
|
+
}
|
package/src/utils/Logger.mjs
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
import chalk from 'chalk';
|
2
|
-
|
3
|
-
export class Logger {
|
4
|
-
static error(message) {
|
5
|
-
console.log(`${chalk.bgRed(' ERROR ')} ${message}`);
|
6
|
-
}
|
7
|
-
}
|
1
|
+
import chalk from 'chalk';
|
2
|
+
|
3
|
+
export class Logger {
|
4
|
+
static error(message) {
|
5
|
+
console.log(`${chalk.bgRed(' ERROR ')} ${message}`);
|
6
|
+
}
|
7
|
+
}
|
package/src/utils/validation.mjs
CHANGED
@@ -1,37 +1,37 @@
|
|
1
|
-
import chalk from 'chalk';
|
2
|
-
import { existsSync } from 'fs';
|
3
|
-
import path from 'path';
|
4
|
-
|
5
|
-
import { Logger } from './Logger.mjs';
|
6
|
-
|
7
|
-
export function validateOptions(opts) {
|
8
|
-
if (!Object.keys(opts).length) {
|
9
|
-
Logger.error(`Invalid arguments. Use ${chalk.gray('--help')} for usage`);
|
10
|
-
|
11
|
-
process.exit(1);
|
12
|
-
}
|
13
|
-
|
14
|
-
const { to, from, dir, genType } = opts;
|
15
|
-
|
16
|
-
if ((from && !to) || (to && !from)) {
|
17
|
-
Logger.error(
|
18
|
-
`${chalk.gray('--from')} and ${chalk.gray('--to')} are dependent options`
|
19
|
-
);
|
20
|
-
process.exit(1);
|
21
|
-
}
|
22
|
-
|
23
|
-
const inputFile = path.join(process.cwd(), dir, `${from}.json`);
|
24
|
-
const genTypeFile = path.join(process.cwd(), dir, `${genType}.json`);
|
25
|
-
|
26
|
-
if (!existsSync(inputFile) && from) {
|
27
|
-
Logger.error(`File "${inputFile}" not found`);
|
28
|
-
process.exit(1);
|
29
|
-
}
|
30
|
-
|
31
|
-
if (!existsSync(genTypeFile) && genType) {
|
32
|
-
Logger.error(`File "${genTypeFile}" not found`);
|
33
|
-
process.exit(1);
|
34
|
-
}
|
35
|
-
|
36
|
-
return { ...opts, inputFile, genTypeFile };
|
37
|
-
}
|
1
|
+
import chalk from 'chalk';
|
2
|
+
import { existsSync } from 'fs';
|
3
|
+
import path from 'path';
|
4
|
+
|
5
|
+
import { Logger } from './Logger.mjs';
|
6
|
+
|
7
|
+
export function validateOptions(opts) {
|
8
|
+
if (!Object.keys(opts).length) {
|
9
|
+
Logger.error(`Invalid arguments. Use ${chalk.gray('--help')} for usage`);
|
10
|
+
|
11
|
+
process.exit(1);
|
12
|
+
}
|
13
|
+
|
14
|
+
const { to, from, dir, genType } = opts;
|
15
|
+
|
16
|
+
if ((from && !to) || (to && !from)) {
|
17
|
+
Logger.error(
|
18
|
+
`${chalk.gray('--from')} and ${chalk.gray('--to')} are dependent options`
|
19
|
+
);
|
20
|
+
process.exit(1);
|
21
|
+
}
|
22
|
+
|
23
|
+
const inputFile = path.join(process.cwd(), dir, `${from}.json`);
|
24
|
+
const genTypeFile = path.join(process.cwd(), dir, `${genType}.json`);
|
25
|
+
|
26
|
+
if (!existsSync(inputFile) && from) {
|
27
|
+
Logger.error(`File "${inputFile}" not found`);
|
28
|
+
process.exit(1);
|
29
|
+
}
|
30
|
+
|
31
|
+
if (!existsSync(genTypeFile) && genType) {
|
32
|
+
Logger.error(`File "${genTypeFile}" not found`);
|
33
|
+
process.exit(1);
|
34
|
+
}
|
35
|
+
|
36
|
+
return { ...opts, inputFile, genTypeFile };
|
37
|
+
}
|