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 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
- ```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
- };
165
-
166
- translate('GENERAL.ACCEPT'); // Intellisense and type check from GlobalTranslation
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-lang",
3
- "version": "1.0.7",
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.7';
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
+ }