auto-lang 1.0.8 → 1.1.1

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.1",
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,138 @@
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 pJson from '../package.json' assert { type: "json" };
12
+ import {Logger} from './utils/Logger.mjs';
13
+ import {validateOptions} from './utils/validation.mjs';
14
+
15
+ const {version: appVersion} = pJson;
16
+
17
+
18
+ const program = new Command();
19
+ const nodeMajVer = parseInt(process.version.substring(1).split('.')[0]);
20
+
21
+ if (nodeMajVer < 14) {
22
+ Logger.error(`Node version >= 14.x.x is required`);
23
+
24
+ process.exit(1);
25
+ }
26
+
27
+ program
28
+ .name('auto-lang')
29
+ .description('Generate translation files for multiple languages (i18n)')
30
+ .version(appVersion)
31
+ .option('-f, --from <lang>', 'language to translate from')
32
+ .option(
33
+ '-t, --to <lang...>',
34
+ 'languages to translate to (seperated by space)'
35
+ )
36
+ .option(
37
+ '-d, --dir <directory>',
38
+ 'directory containing the language files',
39
+ 'translations'
40
+ )
41
+ .option('-s, --skip-existing', 'skip existing keys during translation')
42
+ .option('-g, --gen-type <lang>', 'generate types from language file')
43
+ .parse();
44
+
45
+ const { from, to, genType, inputFile, genTypeFile, dir, skipExisting } = 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] = obj2[key] || {};
57
+ await makeTranslatedCopy(value, obj2[key], options);
58
+ } else {
59
+ try {
60
+ if(!(obj2[key] && skipExisting)) 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
+ let translatedObj = {};
73
+ const outputFile = path.join(process.cwd(), dir, `${language}.json`);
74
+
75
+ if(existsSync(outputFile)) {
76
+ translatedObj = JSON.parse(
77
+ await fs.readFile(outputFile, { encoding: 'utf-8' })
78
+ );
79
+ }
80
+
81
+ await makeTranslatedCopy(inputJson, translatedObj, { to: language });
82
+
83
+ resolve(JSON.stringify(translatedObj, null, 4));
84
+ });
85
+
86
+ async function createDeclarationFile() {
87
+ const spinner = createSpinner('Creating language type file').start();
88
+
89
+ const interfaces = JsonToTS(inputJson, { rootName: 'GlobalTranslationType' });
90
+ const typesDir = path.join(process.cwd(), dir, 'types');
91
+
92
+ if (!existsSync(typesDir)) {
93
+ fs.mkdir(typesDir);
94
+ }
95
+
96
+ const declarationFile = path.join(typesDir, 'index.ts');
97
+
98
+ const result = `
99
+ type NestedKeyOf<ObjectType extends object> = {
100
+ [Key in keyof ObjectType & string]: ObjectType[Key] extends object
101
+ ? // @ts-ignore
102
+ \`$\{Key}.$\{NestedKeyOf<ObjectType[Key]>}\`
103
+ : \`$\{Key}\`
104
+ }[keyof ObjectType & string]
105
+
106
+ export type GlobalTranslation = NestedKeyOf<GlobalTranslationType>;
107
+
108
+ ${interfaces.join('\n\n')}
109
+ `;
110
+
111
+ const formattedResult = prettier.format(result, { parser: 'typescript' });
112
+
113
+ fs.writeFile(declarationFile, formattedResult);
114
+ spinner.success({ text: 'Language type file created' });
115
+ }
116
+
117
+ async function translateFile() {
118
+ let spinner, langFile, tranlatedJson;
119
+
120
+ for (let lang of to) {
121
+ langFile = path.join(process.cwd(), dir, `${lang}.json`);
122
+ spinner = createSpinner(`Translating to ${lang}...`).start();
123
+
124
+ tranlatedJson = await getTranslation(lang);
125
+ // await sleep(1000);
126
+ await fs.writeFile(langFile, tranlatedJson);
127
+
128
+ spinner.success({ text: `Complete` });
129
+ }
130
+ }
131
+
132
+ if (from && to) {
133
+ await translateFile();
134
+ }
135
+
136
+ if (genType) {
137
+ await createDeclarationFile();
138
+ }
@@ -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
+ }