auto-lang 1.0.8 → 1.1.0

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