@diplodoc/cli 4.13.4 → 4.13.6

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "author": "Yandex Data UI Team <data-ui@yandex-team.ru>",
4
4
  "description": "Make documentation using yfm-docs in Markdown and HTML formats",
5
5
  "license": "MIT",
6
- "version": "4.13.4",
6
+ "version": "4.13.6",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git@github.com:diplodoc-platform/cli.git"
@@ -31,9 +31,9 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@aws-sdk/client-s3": "^3.369.0",
34
- "@diplodoc/client": "^2.0.4",
35
- "@diplodoc/latex-extension": "^1.0.3",
36
- "@diplodoc/markdown-translation": "^2.0.0-alpha-2",
34
+ "@diplodoc/client": "^2.0.5",
35
+ "@diplodoc/latex-extension": "^1.1.0",
36
+ "@diplodoc/markdown-translation": "^2.0.0-beta-1",
37
37
  "@diplodoc/mermaid-extension": "^1.2.1",
38
38
  "@diplodoc/openapi-extension": "^1.4.13",
39
39
  "@diplodoc/transform": "^4.8.2",
@@ -0,0 +1,311 @@
1
+ import {Arguments} from 'yargs';
2
+ import {ArgvService} from '../../services';
3
+ import {logger} from '../../utils';
4
+ import {ok} from 'assert';
5
+ import {basename, dirname, join, resolve} from 'path';
6
+ import glob from 'glob';
7
+ import {getYandexOAuthToken} from '../../packages/credentials';
8
+ import {asyncify, eachLimit, retry} from 'async';
9
+ import {Session} from '@yandex-cloud/nodejs-sdk/dist/session';
10
+ import {TranslationServiceClient} from '@yandex-cloud/nodejs-sdk/dist/generated/yandex/cloud/service_clients';
11
+ import {
12
+ TranslateRequest_Format as Format,
13
+ TranslateRequest,
14
+ } from '@yandex-cloud/nodejs-sdk/dist/generated/yandex/cloud/ai/translate/v2/translation_service';
15
+ import {mkdir, readFile, writeFile} from 'fs/promises';
16
+
17
+ // @ts-ignore
18
+ import {compose, extract} from '@diplodoc/markdown-translation';
19
+
20
+ const REQUESTS_LIMIT = 20;
21
+ const BYTES_LIMIT = 10000;
22
+ const RETRY_LIMIT = 3;
23
+
24
+ class TranslatorError extends Error {
25
+ path: string;
26
+
27
+ constructor(message: string, path: string) {
28
+ super(message);
29
+
30
+ this.path = path;
31
+ }
32
+ }
33
+
34
+ type TranslateConfig = {
35
+ input: string;
36
+ output?: string;
37
+ sourceLanguage: string;
38
+ targetLanguage: string;
39
+ folderId?: string;
40
+ glossary?: string;
41
+ include?: string[];
42
+ exclude?: string[];
43
+ };
44
+
45
+ export async function handler(args: Arguments<any>) {
46
+ ArgvService.init({
47
+ ...(args.translate || {}),
48
+ ...args,
49
+ });
50
+
51
+ const {
52
+ folderId,
53
+ // yandexCloudTranslateGlossaryPairs,
54
+ sourceLanguage,
55
+ targetLanguage,
56
+ exclude = [],
57
+ } = ArgvService.getConfig() as unknown as TranslateConfig;
58
+
59
+ let {input, output, include = []} = ArgvService.getConfig() as unknown as TranslateConfig;
60
+
61
+ logger.info(
62
+ input,
63
+ `translating documentation from ${sourceLanguage} to ${targetLanguage} language`,
64
+ );
65
+
66
+ output = output || input;
67
+
68
+ ok(input, 'Required param input is not configured');
69
+ ok(sourceLanguage, 'Required param sourceLanguage is not configured');
70
+ ok(targetLanguage, 'Required param targetLanguage is not configured');
71
+
72
+ try {
73
+ if (input.endsWith('.md')) {
74
+ include = [basename(input)];
75
+ input = dirname(input);
76
+ } else if (!include.length) {
77
+ include.push('**/*');
78
+ }
79
+
80
+ const files = ([] as string[]).concat(
81
+ ...include.map((match) =>
82
+ glob.sync(match, {
83
+ cwd: join(input, sourceLanguage),
84
+ ignore: exclude,
85
+ }),
86
+ ),
87
+ );
88
+ const found = [...new Set(files)];
89
+
90
+ const oauthToken = await getYandexOAuthToken();
91
+
92
+ const translatorParams = {
93
+ input,
94
+ output,
95
+ sourceLanguage,
96
+ targetLanguage,
97
+ // yandexCloudTranslateGlossaryPairs,
98
+ folderId,
99
+ oauthToken,
100
+ };
101
+
102
+ const translateFn = translator(translatorParams);
103
+
104
+ await eachLimit(found, REQUESTS_LIMIT, asyncify(translateFn));
105
+ } catch (err) {
106
+ if (err instanceof Error || err instanceof TranslatorError) {
107
+ const message = err.message;
108
+
109
+ const file = err instanceof TranslatorError ? err.path : '';
110
+
111
+ logger.error(file, message);
112
+ }
113
+ }
114
+
115
+ logger.info(
116
+ output,
117
+ `translated documentation from ${sourceLanguage} to ${targetLanguage} language`,
118
+ );
119
+ }
120
+
121
+ export type TranslatorParams = {
122
+ oauthToken: string;
123
+ folderId: string | undefined;
124
+ input: string;
125
+ output: string;
126
+ sourceLanguage: string;
127
+ targetLanguage: string;
128
+ // yandexCloudTranslateGlossaryPairs: YandexCloudTranslateGlossaryPair[];
129
+ };
130
+
131
+ function translator(params: TranslatorParams) {
132
+ const {
133
+ oauthToken,
134
+ folderId,
135
+ input,
136
+ output,
137
+ sourceLanguage,
138
+ targetLanguage,
139
+ // yandexCloudTranslateGlossaryPairs,
140
+ } = params;
141
+
142
+ const tmap = new Map<string, Defer>();
143
+ const session = new Session({oauthToken});
144
+ const client = session.client(TranslationServiceClient);
145
+ const request = (texts: string[]) => () => {
146
+ return client
147
+ .translate(
148
+ TranslateRequest.fromPartial({
149
+ texts,
150
+ folderId,
151
+ sourceLanguageCode: sourceLanguage,
152
+ targetLanguageCode: targetLanguage,
153
+ // glossaryConfig: {
154
+ // glossaryData: {
155
+ // glossaryPairs: yandexCloudTranslateGlossaryPairs,
156
+ // },
157
+ // },
158
+ format: Format.HTML,
159
+ }),
160
+ )
161
+ .then((results) => {
162
+ return results.translations.map(({text}, index) => {
163
+ const defer = tmap.get(texts[index]);
164
+ if (defer) {
165
+ defer.resolve([text]);
166
+ }
167
+
168
+ return text;
169
+ });
170
+ });
171
+ };
172
+
173
+ return async (mdPath: string) => {
174
+ if (!mdPath.endsWith('.md')) {
175
+ return;
176
+ }
177
+
178
+ try {
179
+ logger.info(mdPath, 'translating');
180
+
181
+ const inputPath = resolve(input, sourceLanguage, mdPath);
182
+ const outputPath = resolve(output, targetLanguage, mdPath);
183
+ const md = await readFile(inputPath, {encoding: 'utf-8'});
184
+
185
+ await mkdir(dirname(outputPath), {recursive: true});
186
+
187
+ if (!md) {
188
+ await writeFile(outputPath, md);
189
+ return;
190
+ }
191
+
192
+ const {units, skeleton} = extract({
193
+ source: {
194
+ language: sourceLanguage,
195
+ locale: 'RU',
196
+ },
197
+ target: {
198
+ language: targetLanguage,
199
+ locale: 'US',
200
+ },
201
+ markdown: md,
202
+ markdownPath: mdPath,
203
+ skeletonPath: '',
204
+ });
205
+
206
+ if (!units.length) {
207
+ await writeFile(outputPath, md);
208
+ return;
209
+ }
210
+
211
+ const parts = await Promise.all(
212
+ (units as string[]).reduce(
213
+ (
214
+ {
215
+ promises,
216
+ buffer,
217
+ bufferSize,
218
+ }: {
219
+ promises: Promise<string[]>[];
220
+ buffer: string[];
221
+ bufferSize: number;
222
+ },
223
+ text,
224
+ index,
225
+ ) => {
226
+ if (text.length >= BYTES_LIMIT) {
227
+ logger.warn(
228
+ mdPath,
229
+ 'Skip document part for translation. Part is too big.',
230
+ );
231
+ promises.push(Promise.resolve([text]));
232
+ return {promises, buffer, bufferSize};
233
+ }
234
+
235
+ const defer = tmap.get(text);
236
+ if (defer) {
237
+ console.log('SKIPPED', text);
238
+ promises.push(defer.promise);
239
+ return {promises, buffer, bufferSize};
240
+ }
241
+
242
+ if (bufferSize + text.length > BYTES_LIMIT) {
243
+ promises.push(backoff(request(buffer)));
244
+ buffer = [];
245
+ bufferSize = 0;
246
+ }
247
+
248
+ buffer.push(text);
249
+ bufferSize += text.length;
250
+ tmap.set(text, new Defer());
251
+
252
+ if (index === units.length - 1) {
253
+ promises.push(backoff(request(buffer)));
254
+ }
255
+
256
+ return {promises, buffer, bufferSize};
257
+ },
258
+ {
259
+ promises: [],
260
+ buffer: [],
261
+ bufferSize: 0,
262
+ },
263
+ ).promises,
264
+ );
265
+
266
+ const translations = ([] as string[]).concat(...parts);
267
+
268
+ const composed = await compose({
269
+ useSource: true,
270
+ units: translations,
271
+ skeleton,
272
+ });
273
+
274
+ await writeFile(outputPath, composed);
275
+
276
+ logger.info(outputPath, 'finished translating');
277
+ } catch (err) {
278
+ if (err instanceof Error) {
279
+ throw new TranslatorError(err.toString(), mdPath);
280
+ }
281
+ }
282
+ };
283
+ }
284
+
285
+ function backoff(action: () => Promise<string[]>): Promise<string[]> {
286
+ return retry(
287
+ {
288
+ times: RETRY_LIMIT,
289
+ interval: (count: number) => {
290
+ // eslint-disable-next-line no-bitwise
291
+ return (1 << count) * 1000;
292
+ },
293
+ },
294
+ asyncify(action),
295
+ );
296
+ }
297
+
298
+ class Defer {
299
+ resolve!: (text: string[]) => void;
300
+
301
+ reject!: (error: any) => void;
302
+
303
+ promise: Promise<string[]>;
304
+
305
+ constructor() {
306
+ this.promise = new Promise((resolve, reject) => {
307
+ this.resolve = resolve;
308
+ this.reject = reject;
309
+ });
310
+ }
311
+ }
@@ -1,27 +1,9 @@
1
- import glob from 'glob';
2
- import {ok} from 'assert';
3
- import {asyncify, eachLimit, retry} from 'async';
1
+ import {extract} from '../xliff/extract';
2
+ import {compose} from '../xliff/compose';
3
+ import {handler} from './handler';
4
4
 
5
- import {basename, dirname, join, resolve} from 'path';
6
- import {mkdir, readFile, writeFile} from 'fs/promises';
5
+ import {Argv} from 'yargs';
7
6
 
8
- import {Session} from '@yandex-cloud/nodejs-sdk/dist/session';
9
- import {TranslationServiceClient} from '@yandex-cloud/nodejs-sdk/dist/generated/yandex/cloud/service_clients';
10
- import {
11
- TranslateRequest_Format as Format,
12
- TranslateRequest,
13
- } from '@yandex-cloud/nodejs-sdk/dist/generated/yandex/cloud/ai/translate/v2/translation_service';
14
-
15
- // @ts-ignore
16
- import {compose, extract} from '@diplodoc/markdown-translation';
17
-
18
- import {ArgvService} from '../../services';
19
- import {getYandexOAuthToken} from '../../packages/credentials';
20
- import {logger} from '../../utils';
21
-
22
- import {Arguments, Argv} from 'yargs';
23
-
24
- // import {YandexCloudTranslateGlossaryPair} from '../../models';
25
7
  import {argvValidator} from '../../validator';
26
8
 
27
9
  const command = 'translate';
@@ -35,12 +17,10 @@ const translate = {
35
17
  builder,
36
18
  };
37
19
 
38
- const REQUESTS_LIMIT = 20;
39
- const BYTES_LIMIT = 10000;
40
- const RETRY_LIMIT = 3;
41
-
42
20
  function builder<T>(argv: Argv<T>) {
43
21
  return argv
22
+ .command(extract)
23
+ .command(compose)
44
24
  .option('folder-id', {
45
25
  describe: 'folder id',
46
26
  type: 'string',
@@ -55,301 +35,15 @@ function builder<T>(argv: Argv<T>) {
55
35
  describe: 'target language code',
56
36
  type: 'string',
57
37
  })
38
+ .option('include', {
39
+ describe: 'relative to input globs to include in processing',
40
+ type: 'string',
41
+ })
42
+ .option('exclude', {
43
+ describe: 'relative to input globs to exclude from processing',
44
+ type: 'string',
45
+ })
58
46
  .check(argvValidator);
59
- // .demandOption(
60
- // ['source-language', 'target-language'],
61
- // 'command requires to specify source and target languages',
62
- // );
63
- }
64
-
65
- class TranslatorError extends Error {
66
- path: string;
67
-
68
- constructor(message: string, path: string) {
69
- super(message);
70
-
71
- this.path = path;
72
- }
73
- }
74
-
75
- type TranslateConfig = {
76
- input: string;
77
- output?: string;
78
- sourceLanguage: string;
79
- targetLanguage: string;
80
- folderId?: string;
81
- glossary?: string;
82
- include?: string[];
83
- exclude?: string[];
84
- };
85
-
86
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
87
- async function handler(args: Arguments<any>) {
88
- ArgvService.init({
89
- ...(args.translate || {}),
90
- ...args,
91
- });
92
-
93
- const {
94
- folderId,
95
- // yandexCloudTranslateGlossaryPairs,
96
- sourceLanguage,
97
- targetLanguage,
98
- exclude = [],
99
- } = ArgvService.getConfig() as unknown as TranslateConfig;
100
-
101
- let {input, output, include = []} = ArgvService.getConfig() as unknown as TranslateConfig;
102
-
103
- logger.info(
104
- input,
105
- `translating documentation from ${sourceLanguage} to ${targetLanguage} language`,
106
- );
107
-
108
- output = output || input;
109
-
110
- ok(input, 'Required param input is not configured');
111
- ok(sourceLanguage, 'Required param sourceLanguage is not configured');
112
- ok(targetLanguage, 'Required param targetLanguage is not configured');
113
-
114
- try {
115
- if (input.endsWith('.md')) {
116
- include = [basename(input)];
117
- input = dirname(input);
118
- } else if (!include.length) {
119
- include.push('**/*');
120
- }
121
-
122
- const files = ([] as string[]).concat(
123
- ...include.map((match) =>
124
- glob.sync(match, {
125
- cwd: join(input, sourceLanguage),
126
- ignore: exclude,
127
- }),
128
- ),
129
- );
130
- const found = [...new Set(files)];
131
-
132
- const oauthToken = await getYandexOAuthToken();
133
-
134
- const translatorParams = {
135
- input,
136
- output,
137
- sourceLanguage,
138
- targetLanguage,
139
- // yandexCloudTranslateGlossaryPairs,
140
- folderId,
141
- oauthToken,
142
- };
143
-
144
- const translateFn = translator(translatorParams);
145
-
146
- await eachLimit(found, REQUESTS_LIMIT, asyncify(translateFn));
147
- } catch (err) {
148
- if (err instanceof Error || err instanceof TranslatorError) {
149
- const message = err.message;
150
-
151
- const file = err instanceof TranslatorError ? err.path : '';
152
-
153
- logger.error(file, message);
154
- }
155
- }
156
-
157
- logger.info(
158
- output,
159
- `translated documentation from ${sourceLanguage} to ${targetLanguage} language`,
160
- );
161
- }
162
-
163
- export type TranslatorParams = {
164
- oauthToken: string;
165
- folderId: string | undefined;
166
- input: string;
167
- output: string;
168
- sourceLanguage: string;
169
- targetLanguage: string;
170
- // yandexCloudTranslateGlossaryPairs: YandexCloudTranslateGlossaryPair[];
171
- };
172
-
173
- function translator(params: TranslatorParams) {
174
- const {
175
- oauthToken,
176
- folderId,
177
- input,
178
- output,
179
- sourceLanguage,
180
- targetLanguage,
181
- // yandexCloudTranslateGlossaryPairs,
182
- } = params;
183
-
184
- const tmap = new Map<string, Defer>();
185
- const session = new Session({oauthToken});
186
- const client = session.client(TranslationServiceClient);
187
- const request = (texts: string[]) => () => {
188
- return client
189
- .translate(
190
- TranslateRequest.fromPartial({
191
- texts,
192
- folderId,
193
- sourceLanguageCode: sourceLanguage,
194
- targetLanguageCode: targetLanguage,
195
- // glossaryConfig: {
196
- // glossaryData: {
197
- // glossaryPairs: yandexCloudTranslateGlossaryPairs,
198
- // },
199
- // },
200
- format: Format.HTML,
201
- }),
202
- )
203
- .then((results) => {
204
- return results.translations.map(({text}, index) => {
205
- const defer = tmap.get(texts[index]);
206
- if (defer) {
207
- defer.resolve([text]);
208
- }
209
-
210
- return text;
211
- });
212
- });
213
- };
214
-
215
- return async (mdPath: string) => {
216
- if (!mdPath.endsWith('.md')) {
217
- return;
218
- }
219
-
220
- try {
221
- logger.info(mdPath, 'translating');
222
-
223
- const inputPath = resolve(input, sourceLanguage, mdPath);
224
- const outputPath = resolve(output, targetLanguage, mdPath);
225
- const md = await readFile(inputPath, {encoding: 'utf-8'});
226
-
227
- await mkdir(dirname(outputPath), {recursive: true});
228
-
229
- if (!md) {
230
- await writeFile(outputPath, md);
231
- return;
232
- }
233
-
234
- const {units, skeleton} = extract({
235
- source: {
236
- language: sourceLanguage,
237
- locale: 'RU',
238
- },
239
- target: {
240
- language: targetLanguage,
241
- locale: 'US',
242
- },
243
- markdown: md,
244
- markdownPath: mdPath,
245
- skeletonPath: '',
246
- });
247
-
248
- if (!units.length) {
249
- await writeFile(outputPath, md);
250
- return;
251
- }
252
-
253
- const parts = await Promise.all(
254
- (units as string[]).reduce(
255
- (
256
- {
257
- promises,
258
- buffer,
259
- bufferSize,
260
- }: {
261
- promises: Promise<string[]>[];
262
- buffer: string[];
263
- bufferSize: number;
264
- },
265
- text,
266
- index,
267
- ) => {
268
- if (text.length >= BYTES_LIMIT) {
269
- logger.warn(
270
- mdPath,
271
- 'Skip document part for translation. Part is too big.',
272
- );
273
- promises.push(Promise.resolve([text]));
274
- return {promises, buffer, bufferSize};
275
- }
276
-
277
- const defer = tmap.get(text);
278
- if (defer) {
279
- console.log('SKIPPED', text);
280
- promises.push(defer.promise);
281
- return {promises, buffer, bufferSize};
282
- }
283
-
284
- if (bufferSize + text.length > BYTES_LIMIT) {
285
- promises.push(backoff(request(buffer)));
286
- buffer = [];
287
- bufferSize = 0;
288
- }
289
-
290
- buffer.push(text);
291
- bufferSize += text.length;
292
- tmap.set(text, new Defer());
293
-
294
- if (index === units.length - 1) {
295
- promises.push(backoff(request(buffer)));
296
- }
297
-
298
- return {promises, buffer, bufferSize};
299
- },
300
- {
301
- promises: [],
302
- buffer: [],
303
- bufferSize: 0,
304
- },
305
- ).promises,
306
- );
307
-
308
- const translations = ([] as string[]).concat(...parts);
309
-
310
- const composed = await compose({
311
- useSource: true,
312
- units: translations,
313
- skeleton,
314
- });
315
-
316
- await writeFile(outputPath, composed);
317
-
318
- logger.info(outputPath, 'finished translating');
319
- } catch (err) {
320
- if (err instanceof Error) {
321
- throw new TranslatorError(err.toString(), mdPath);
322
- }
323
- }
324
- };
325
- }
326
-
327
- function backoff(action: () => Promise<string[]>): Promise<string[]> {
328
- return retry(
329
- {
330
- times: RETRY_LIMIT,
331
- interval: (count: number) => {
332
- // eslint-disable-next-line no-bitwise
333
- return (1 << count) * 1000;
334
- },
335
- },
336
- asyncify(action),
337
- );
338
- }
339
-
340
- class Defer {
341
- resolve!: (text: string[]) => void;
342
-
343
- reject!: (error: any) => void;
344
-
345
- promise: Promise<string[]>;
346
-
347
- constructor() {
348
- this.promise = new Promise((resolve, reject) => {
349
- this.resolve = resolve;
350
- this.reject = reject;
351
- });
352
- }
353
47
  }
354
48
 
355
49
  export {translate};