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