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 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
- 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.4",
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('1.0.4')
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
+ }