@tuhama/translation-manager 0.3.0 → 0.5.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/LICENSE +21 -21
- package/README.md +71 -59
- package/bin/index.js +45 -45
- package/package.json +66 -66
- package/src/core/Scanner.js +128 -128
- package/src/core/Storage.js +82 -82
- package/src/core/TranslatorManager.js +28 -5
- package/src/core/Utilities.js +55 -55
- package/src/core/services/GoogleTranslator.js +40 -22
- package/src/server.js +35 -1
- package/web/dist/assets/{index-MTmK2fr8.js → index-ByUmql2E.js} +1 -1
- package/web/dist/index.html +18 -18
- package/web/package.json +22 -22
package/src/core/Storage.js
CHANGED
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Interface-like class for managing translation file storage.
|
|
6
|
-
* Defaults to JSON files on local filesystem.
|
|
7
|
-
*/
|
|
8
|
-
class Storage {
|
|
9
|
-
constructor(targetDir, config = {}) {
|
|
10
|
-
this.targetDir = targetDir;
|
|
11
|
-
this.config = config;
|
|
12
|
-
this.defaultPaths = [
|
|
13
|
-
'public/locales',
|
|
14
|
-
'src/locales',
|
|
15
|
-
'src/i18n',
|
|
16
|
-
'locales'
|
|
17
|
-
];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Finds and returns the path to the locales directory.
|
|
22
|
-
*/
|
|
23
|
-
async getLocalesDir() {
|
|
24
|
-
let localesDir = this.config.path;
|
|
25
|
-
|
|
26
|
-
if (!localesDir) {
|
|
27
|
-
for (const p of this.defaultPaths) {
|
|
28
|
-
const fullPath = path.resolve(this.targetDir, p);
|
|
29
|
-
if (await fs.pathExists(fullPath) && (await fs.stat(fullPath)).isDirectory()) {
|
|
30
|
-
return fullPath;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
34
|
-
localesDir = path.resolve(this.targetDir, localesDir);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!localesDir || !(await fs.pathExists(localesDir))) {
|
|
38
|
-
throw new Error('Could not find translation directory. Please specify it in the config or ensure it exists.');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return localesDir;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Reads all translation files from the locales directory.
|
|
46
|
-
*/
|
|
47
|
-
async readAll() {
|
|
48
|
-
const localesDir = await this.getLocalesDir();
|
|
49
|
-
const files = await fs.readdir(localesDir);
|
|
50
|
-
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
|
51
|
-
|
|
52
|
-
const translations = {};
|
|
53
|
-
for (const file of jsonFiles) {
|
|
54
|
-
const lang = path.basename(file, '.json');
|
|
55
|
-
const content = await fs.readJson(path.join(localesDir, file));
|
|
56
|
-
translations[lang] = content;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return translations;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Writes a single translation file (or all of them).
|
|
64
|
-
*/
|
|
65
|
-
async write(lang, content) {
|
|
66
|
-
const localesDir = await this.getLocalesDir();
|
|
67
|
-
const filePath = path.join(localesDir, `${lang}.json`);
|
|
68
|
-
await fs.writeJson(filePath, content, { spaces: 2 });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Writes all translations.
|
|
73
|
-
*/
|
|
74
|
-
async writeAll(translations) {
|
|
75
|
-
const languages = Object.keys(translations);
|
|
76
|
-
for (const lang of languages) {
|
|
77
|
-
await this.write(lang, translations[lang]);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
module.exports = Storage;
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface-like class for managing translation file storage.
|
|
6
|
+
* Defaults to JSON files on local filesystem.
|
|
7
|
+
*/
|
|
8
|
+
class Storage {
|
|
9
|
+
constructor(targetDir, config = {}) {
|
|
10
|
+
this.targetDir = targetDir;
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.defaultPaths = [
|
|
13
|
+
'public/locales',
|
|
14
|
+
'src/locales',
|
|
15
|
+
'src/i18n',
|
|
16
|
+
'locales'
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Finds and returns the path to the locales directory.
|
|
22
|
+
*/
|
|
23
|
+
async getLocalesDir() {
|
|
24
|
+
let localesDir = this.config.path;
|
|
25
|
+
|
|
26
|
+
if (!localesDir) {
|
|
27
|
+
for (const p of this.defaultPaths) {
|
|
28
|
+
const fullPath = path.resolve(this.targetDir, p);
|
|
29
|
+
if (await fs.pathExists(fullPath) && (await fs.stat(fullPath)).isDirectory()) {
|
|
30
|
+
return fullPath;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
localesDir = path.resolve(this.targetDir, localesDir);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!localesDir || !(await fs.pathExists(localesDir))) {
|
|
38
|
+
throw new Error('Could not find translation directory. Please specify it in the config or ensure it exists.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return localesDir;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Reads all translation files from the locales directory.
|
|
46
|
+
*/
|
|
47
|
+
async readAll() {
|
|
48
|
+
const localesDir = await this.getLocalesDir();
|
|
49
|
+
const files = await fs.readdir(localesDir);
|
|
50
|
+
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
|
51
|
+
|
|
52
|
+
const translations = {};
|
|
53
|
+
for (const file of jsonFiles) {
|
|
54
|
+
const lang = path.basename(file, '.json');
|
|
55
|
+
const content = await fs.readJson(path.join(localesDir, file));
|
|
56
|
+
translations[lang] = content;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return translations;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Writes a single translation file (or all of them).
|
|
64
|
+
*/
|
|
65
|
+
async write(lang, content) {
|
|
66
|
+
const localesDir = await this.getLocalesDir();
|
|
67
|
+
const filePath = path.join(localesDir, `${lang}.json`);
|
|
68
|
+
await fs.writeJson(filePath, content, { spaces: 2 });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Writes all translations.
|
|
73
|
+
*/
|
|
74
|
+
async writeAll(translations) {
|
|
75
|
+
const languages = Object.keys(translations);
|
|
76
|
+
for (const lang of languages) {
|
|
77
|
+
await this.write(lang, translations[lang]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = Storage;
|
|
@@ -113,7 +113,18 @@ class TranslatorManager {
|
|
|
113
113
|
* Translates a specific text into target language.
|
|
114
114
|
*/
|
|
115
115
|
async translateSingle(text, targetLang, sourceLang = 'en') {
|
|
116
|
-
|
|
116
|
+
// Support both old and new config formats for backward compatibility
|
|
117
|
+
let translatorConfig;
|
|
118
|
+
if (this.config.googleTranslateApiKey) {
|
|
119
|
+
// Legacy v2 config - provide migration guidance
|
|
120
|
+
throw new Error('Google Translate API v2 is deprecated. Please update your configuration to use v3 with projectId and keyFilename in the googleTranslate object.');
|
|
121
|
+
} else if (this.config.googleTranslate) {
|
|
122
|
+
translatorConfig = this.config.googleTranslate;
|
|
123
|
+
} else {
|
|
124
|
+
throw new Error('Google Translate configuration is missing. Please add googleTranslate.projectId and googleTranslate.keyFilename in Settings.');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const translator = new GoogleTranslator(translatorConfig);
|
|
117
128
|
return await translator.translate(text, targetLang, sourceLang);
|
|
118
129
|
}
|
|
119
130
|
|
|
@@ -123,7 +134,7 @@ class TranslatorManager {
|
|
|
123
134
|
async getBulkTranslateReport(sourceLang) {
|
|
124
135
|
const { languages, translations, allKeys } = await this.scan();
|
|
125
136
|
const report = {};
|
|
126
|
-
|
|
137
|
+
|
|
127
138
|
languages.forEach(lang => {
|
|
128
139
|
if (lang === sourceLang) return;
|
|
129
140
|
const missing = allKeys.filter(key => {
|
|
@@ -148,14 +159,26 @@ class TranslatorManager {
|
|
|
148
159
|
async bulkTranslate(sourceLang) {
|
|
149
160
|
const report = await this.getBulkTranslateReport(sourceLang);
|
|
150
161
|
const translations = await this.storage.readAll();
|
|
151
|
-
|
|
152
|
-
|
|
162
|
+
|
|
163
|
+
// Support both old and new config formats for backward compatibility
|
|
164
|
+
let translatorConfig;
|
|
165
|
+
if (this.config.googleTranslateApiKey) {
|
|
166
|
+
// Legacy v2 config - provide migration guidance
|
|
167
|
+
throw new Error('Google Translate API v2 is deprecated. Please update your configuration to use v3 with projectId and keyFilename in the googleTranslate object.');
|
|
168
|
+
} else if (this.config.googleTranslate) {
|
|
169
|
+
translatorConfig = this.config.googleTranslate;
|
|
170
|
+
} else {
|
|
171
|
+
throw new Error('Google Translate configuration is missing. Please add googleTranslate.projectId and googleTranslate.keyFilename in Settings.');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const translator = new GoogleTranslator(translatorConfig);
|
|
175
|
+
|
|
153
176
|
const preview = {};
|
|
154
177
|
|
|
155
178
|
for (const lang in report) {
|
|
156
179
|
const keys = report[lang].keys;
|
|
157
180
|
const sourceTexts = keys.map(key => lodash.get(translations[sourceLang], key));
|
|
158
|
-
|
|
181
|
+
|
|
159
182
|
// Filter out keys that don't have source text
|
|
160
183
|
const validIndices = sourceTexts.map((text, idx) => text ? idx : null).filter(idx => idx !== null);
|
|
161
184
|
const textsToTranslate = validIndices.map(idx => sourceTexts[idx]);
|
package/src/core/Utilities.js
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
const lodash = require('lodash');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Utility functions for object manipulation.
|
|
5
|
-
*/
|
|
6
|
-
class Utilities {
|
|
7
|
-
/**
|
|
8
|
-
* Recursively sorts object keys alphabetically.
|
|
9
|
-
*/
|
|
10
|
-
static sortObject(obj) {
|
|
11
|
-
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
12
|
-
return obj;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const sorted = {};
|
|
16
|
-
Object.keys(obj).sort().forEach(key => {
|
|
17
|
-
sorted[key] = Utilities.sortObject(obj[key]);
|
|
18
|
-
});
|
|
19
|
-
return sorted;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Flattens a nested object into a set of dot-notated keys.
|
|
24
|
-
*/
|
|
25
|
-
static flattenKeys(obj, prefix = '', keySet = new Set()) {
|
|
26
|
-
Object.keys(obj).forEach(key => {
|
|
27
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
28
|
-
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
29
|
-
Utilities.flattenKeys(obj[key], fullKey, keySet);
|
|
30
|
-
} else {
|
|
31
|
-
keySet.add(fullKey);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
return keySet;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Normalizes translation keys across all languages.
|
|
39
|
-
*/
|
|
40
|
-
static syncKeys(translations, allKeys) {
|
|
41
|
-
const languages = Object.keys(translations);
|
|
42
|
-
languages.forEach(lang => {
|
|
43
|
-
const content = translations[lang];
|
|
44
|
-
allKeys.forEach(key => {
|
|
45
|
-
if (lodash.get(content, key) === undefined) {
|
|
46
|
-
lodash.set(content, key, '');
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
translations[lang] = Utilities.sortObject(content);
|
|
50
|
-
});
|
|
51
|
-
return translations;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
module.exports = Utilities;
|
|
1
|
+
const lodash = require('lodash');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utility functions for object manipulation.
|
|
5
|
+
*/
|
|
6
|
+
class Utilities {
|
|
7
|
+
/**
|
|
8
|
+
* Recursively sorts object keys alphabetically.
|
|
9
|
+
*/
|
|
10
|
+
static sortObject(obj) {
|
|
11
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const sorted = {};
|
|
16
|
+
Object.keys(obj).sort().forEach(key => {
|
|
17
|
+
sorted[key] = Utilities.sortObject(obj[key]);
|
|
18
|
+
});
|
|
19
|
+
return sorted;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Flattens a nested object into a set of dot-notated keys.
|
|
24
|
+
*/
|
|
25
|
+
static flattenKeys(obj, prefix = '', keySet = new Set()) {
|
|
26
|
+
Object.keys(obj).forEach(key => {
|
|
27
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
28
|
+
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
29
|
+
Utilities.flattenKeys(obj[key], fullKey, keySet);
|
|
30
|
+
} else {
|
|
31
|
+
keySet.add(fullKey);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return keySet;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Normalizes translation keys across all languages.
|
|
39
|
+
*/
|
|
40
|
+
static syncKeys(translations, allKeys) {
|
|
41
|
+
const languages = Object.keys(translations);
|
|
42
|
+
languages.forEach(lang => {
|
|
43
|
+
const content = translations[lang];
|
|
44
|
+
allKeys.forEach(key => {
|
|
45
|
+
if (lodash.get(content, key) === undefined) {
|
|
46
|
+
lodash.set(content, key, '');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
translations[lang] = Utilities.sortObject(content);
|
|
50
|
+
});
|
|
51
|
+
return translations;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = Utilities;
|
|
@@ -1,12 +1,30 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { TranslationServiceClient } = require('@google-cloud/translate');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Service for interacting with Google Cloud Translation API.
|
|
4
|
+
* Service for interacting with Google Cloud Translation API v3.
|
|
5
5
|
*/
|
|
6
6
|
class GoogleTranslator {
|
|
7
|
-
constructor(
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
constructor(config) {
|
|
8
|
+
// Support both old API key format and new v3 config
|
|
9
|
+
if (typeof config === 'string') {
|
|
10
|
+
// Legacy API key - throw error to guide migration
|
|
11
|
+
throw new Error('Google Translate API v2 is deprecated. Please update your configuration to use v3 with projectId and keyFilename.');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.projectId = config.projectId;
|
|
15
|
+
this.keyFilename = config.keyFilename;
|
|
16
|
+
|
|
17
|
+
if (!this.projectId) {
|
|
18
|
+
throw new Error('Google Cloud Project ID is required for Translate API v3. Please add it in Settings.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Initialize the translate client
|
|
22
|
+
const clientConfig = { projectId: this.projectId };
|
|
23
|
+
if (this.keyFilename) {
|
|
24
|
+
clientConfig.keyFilename = this.keyFilename;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.client = new TranslationServiceClient(clientConfig);
|
|
10
28
|
}
|
|
11
29
|
|
|
12
30
|
/**
|
|
@@ -16,8 +34,8 @@ class GoogleTranslator {
|
|
|
16
34
|
* @param {string} sourceLang - Source language code (e.g. 'en')
|
|
17
35
|
*/
|
|
18
36
|
async translate(text, targetLang, sourceLang = 'en') {
|
|
19
|
-
if (!this.
|
|
20
|
-
throw new Error('Google
|
|
37
|
+
if (!this.projectId) {
|
|
38
|
+
throw new Error('Google Cloud Project ID is missing. Please add it in Settings.');
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
if (!text || (Array.isArray(text) && text.length === 0)) {
|
|
@@ -25,24 +43,24 @@ class GoogleTranslator {
|
|
|
25
43
|
}
|
|
26
44
|
|
|
27
45
|
try {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const translations = response.
|
|
39
|
-
|
|
46
|
+
const location = 'global';
|
|
47
|
+
const request = {
|
|
48
|
+
parent: `projects/${this.projectId}/locations/${location}`,
|
|
49
|
+
contents: Array.isArray(text) ? text : [text],
|
|
50
|
+
mimeType: 'text/plain',
|
|
51
|
+
sourceLanguageCode: sourceLang,
|
|
52
|
+
targetLanguageCode: targetLang,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const [response] = await this.client.translateText(request);
|
|
56
|
+
const translations = response.translations.map(t => t.translatedText);
|
|
57
|
+
|
|
40
58
|
if (Array.isArray(text)) {
|
|
41
|
-
return translations
|
|
59
|
+
return translations;
|
|
42
60
|
}
|
|
43
|
-
return translations[0]
|
|
61
|
+
return translations[0];
|
|
44
62
|
} catch (error) {
|
|
45
|
-
const message = error.
|
|
63
|
+
const message = error.message || 'Unknown translation error';
|
|
46
64
|
throw new Error(`Google Translate Error: ${message}`);
|
|
47
65
|
}
|
|
48
66
|
}
|
package/src/server.js
CHANGED
|
@@ -10,7 +10,7 @@ const TranslatorManager = require('./core/TranslatorManager');
|
|
|
10
10
|
function startServer(targetDir, port = 3000, config = {}) {
|
|
11
11
|
const app = express();
|
|
12
12
|
const manager = new TranslatorManager(targetDir, config);
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
app.use(cors());
|
|
15
15
|
app.use(express.json());
|
|
16
16
|
|
|
@@ -66,9 +66,26 @@ function startServer(targetDir, port = 3000, config = {}) {
|
|
|
66
66
|
app.post('/api/translate', async (req, res) => {
|
|
67
67
|
try {
|
|
68
68
|
const { text, targetLang, sourceLang } = req.body;
|
|
69
|
+
|
|
70
|
+
// Check if Google Translate is configured
|
|
71
|
+
if (!manager.config.googleTranslate || !manager.config.googleTranslate.projectId) {
|
|
72
|
+
return res.status(400).json({
|
|
73
|
+
error: 'Google Translate is not configured. Please add your Google Cloud Project ID and key file in Settings.',
|
|
74
|
+
configurationRequired: true
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
69
78
|
const translatedText = await manager.translateSingle(text, targetLang, sourceLang);
|
|
70
79
|
res.json({ translatedText });
|
|
71
80
|
} catch (err) {
|
|
81
|
+
// Check if it's a configuration error
|
|
82
|
+
if (err.message.includes('Google Cloud Project ID is required') ||
|
|
83
|
+
err.message.includes('Google Translate configuration is missing')) {
|
|
84
|
+
return res.status(400).json({
|
|
85
|
+
error: err.message,
|
|
86
|
+
configurationRequired: true
|
|
87
|
+
});
|
|
88
|
+
}
|
|
72
89
|
res.status(500).json({ error: err.message });
|
|
73
90
|
}
|
|
74
91
|
});
|
|
@@ -86,9 +103,26 @@ function startServer(targetDir, port = 3000, config = {}) {
|
|
|
86
103
|
app.post('/api/bulk-translate/execute', async (req, res) => {
|
|
87
104
|
try {
|
|
88
105
|
const { sourceLang } = req.body;
|
|
106
|
+
|
|
107
|
+
// Check if Google Translate is configured
|
|
108
|
+
if (!manager.config.googleTranslate || !manager.config.googleTranslate.projectId) {
|
|
109
|
+
return res.status(400).json({
|
|
110
|
+
error: 'Google Translate is not configured. Please add your Google Cloud Project ID and key file in Settings.',
|
|
111
|
+
configurationRequired: true
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
89
115
|
const preview = await manager.bulkTranslate(sourceLang || 'en');
|
|
90
116
|
res.json(preview);
|
|
91
117
|
} catch (err) {
|
|
118
|
+
// Check if it's a configuration error
|
|
119
|
+
if (err.message.includes('Google Cloud Project ID is required') ||
|
|
120
|
+
err.message.includes('Google Translate configuration is missing')) {
|
|
121
|
+
return res.status(400).json({
|
|
122
|
+
error: err.message,
|
|
123
|
+
configurationRequired: true
|
|
124
|
+
});
|
|
125
|
+
}
|
|
92
126
|
res.status(500).json({ error: err.message });
|
|
93
127
|
}
|
|
94
128
|
});
|