auto-lang 1.0.3 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -1,19 +1,168 @@
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
- ```$ npm install auto-lang```
8
+ $ npm install auto-lang
7
9
  ### Using yarn
8
- ```$ yarn add auto-lang```
10
+ $ yarn add auto-lang
9
11
 
10
12
  ## Usage
11
- Run `auto-lang [options]`
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
- ### Options
166
+ translate('GENERAL.ACCEPT'); // Intellisense and type check from GlobalTranslation
14
167
 
15
- -V, --version output the version number
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",
3
+ "version": "1.0.6",
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.6';
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('1.0.3')
28
- .requiredOption('-f, --from <lang>', 'language to translate from')
29
- .requiredOption(
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
- 'Languages to translate to (Seperated by space)'
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-types', 'generate typescript declaration file')
42
+ .option('-g, --gen-type <lang>', 'generate types from language file')
34
43
  .parse();
35
44
 
36
- const { from, to, genTypes } = program.opts();
45
+ const { from, to, genType, inputFile, genTypeFile, dir } = validateOptions(
46
+ program.opts()
47
+ );
37
48
 
38
- const inputFile = path.join(process.cwd(), 'translations', `${from}.json`);
39
-
40
- if (!existsSync(inputFile)) {
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
- obj2[key] = await translate(value, { from, to: options.to });
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 = (inputJson, language) =>
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(json) {
66
- const spinner = createSpinner('Creating typescript declaration file').start();
79
+ async function createDeclarationFile() {
80
+ const spinner = createSpinner('Creating language type file').start();
67
81
 
68
- const interfaces = JsonToTS(json, { rootName: 'GlobalTranslation' });
69
- const typesDir = path.join(process.cwd(), 'translations', 'types');
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.d.ts');
89
+ const declarationFile = path.join(typesDir, 'index.ts');
76
90
 
77
91
  const result = `
78
- /* eslint-disable no-var */
79
-
80
- declare global {
81
- ${interfaces[0]}\n\n
82
- }
83
-
84
- ${interfaces.slice(1).join('\n\n')}
85
-
86
- export {};
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: 'TS declaration file created' });
107
+ spinner.success({ text: 'Language type file created' });
93
108
  }
94
109
 
95
- async function translateFile(inputFile) {
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(), 'translations', `${lang}.json`);
114
+ langFile = path.join(process.cwd(), dir, `${lang}.json`);
103
115
  spinner = createSpinner(`Translating to ${lang}...`).start();
104
116
 
105
- tranlatedJson = await getTranslation(inputJson, lang);
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
- const json = await translateFile(inputFile);
125
+ if (from && to) {
126
+ await translateFile();
127
+ }
116
128
 
117
- if (genTypes) {
118
- await createDeclarationFile(json);
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
+ }