@red-hat-developer-hub/backstage-plugin-translations-backend 0.0.2
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/CHANGELOG.md +9 -0
- package/README.md +37 -0
- package/dist/index.cjs.js +12 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/plugin.cjs.js +27 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/service/TranslationService.cjs.js +225 -0
- package/dist/service/TranslationService.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +41 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/dist/utils.cjs.js +48 -0
- package/dist/utils.cjs.js.map +1 -0
- package/package.json +64 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# translations
|
|
2
|
+
|
|
3
|
+
Welcome to the translation backend plugin!
|
|
4
|
+
|
|
5
|
+
This plugin provides a simple backend service in RHDH that serves translation files by returning their JSON content. It exposes an endpoint (/api/translation) and reads the specified JSON translation files.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
This plugin is installed via the `@red-hat-developer-hub/backstage-plugin-translations-backend` package. To install it to your backend package, run the following command:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# From your root directory
|
|
13
|
+
yarn --cwd packages/backend add @red-hat-developer-hub/backstage-plugin-translations-backend
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then add the plugin to your backend in `packages/backend/src/index.ts`:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const backend = createBackend();
|
|
20
|
+
// ...
|
|
21
|
+
backend.add(
|
|
22
|
+
import('@red-hat-developer-hub/backstage-plugin-translations-backend'),
|
|
23
|
+
);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Getting started
|
|
27
|
+
|
|
28
|
+
Your plugin has been added to the backend app in this repository, meaning you'll be able to access it by running `yarn
|
|
29
|
+
start-backend` in the root directory, and then navigating to [/api/translation](http://localhost:7007/api/translation).
|
|
30
|
+
|
|
31
|
+
## Development
|
|
32
|
+
|
|
33
|
+
This plugin backend can be started in a standalone mode from directly in this
|
|
34
|
+
package with `yarn start`. It is a limited setup that is most convenient when
|
|
35
|
+
developing the plugin backend itself.
|
|
36
|
+
|
|
37
|
+
If you want to run the entire project, including the frontend, run `yarn start` from the root directory.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var router = require('./service/router.cjs.js');
|
|
6
|
+
var plugin = require('./plugin.cjs.js');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
exports.createRouter = router.createRouter;
|
|
11
|
+
exports.default = plugin.translationsPlugin;
|
|
12
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
+
import { LoggerService } from '@backstage/backend-plugin-api';
|
|
3
|
+
import { Config } from '@backstage/config';
|
|
4
|
+
import { Router } from 'express';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @public
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
interface RouterOptions {
|
|
11
|
+
logger: LoggerService;
|
|
12
|
+
config: Config;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @public
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
declare function createRouter({ logger, config, }: RouterOptions): Promise<Router>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
declare const translationsPlugin: _backstage_backend_plugin_api.BackendFeature;
|
|
24
|
+
|
|
25
|
+
export { createRouter, translationsPlugin as default };
|
|
26
|
+
export type { RouterOptions };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var router = require('./service/router.cjs.js');
|
|
5
|
+
|
|
6
|
+
const translationsPlugin = backendPluginApi.createBackendPlugin({
|
|
7
|
+
pluginId: "translations",
|
|
8
|
+
register(env) {
|
|
9
|
+
env.registerInit({
|
|
10
|
+
deps: {
|
|
11
|
+
http: backendPluginApi.coreServices.httpRouter,
|
|
12
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
13
|
+
logger: backendPluginApi.coreServices.logger
|
|
14
|
+
},
|
|
15
|
+
async init({ http, config, logger }) {
|
|
16
|
+
http.use(await router.createRouter({ config, logger }));
|
|
17
|
+
http.addAuthPolicy({
|
|
18
|
+
path: "/",
|
|
19
|
+
allow: "unauthenticated"
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
exports.translationsPlugin = translationsPlugin;
|
|
27
|
+
//# sourceMappingURL=plugin.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\n\nimport { createRouter } from './service/router';\n\n/**\n * @public\n */\nexport const translationsPlugin = createBackendPlugin({\n pluginId: 'translations',\n register(env) {\n env.registerInit({\n deps: {\n http: coreServices.httpRouter,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n },\n async init({ http, config, logger }) {\n http.use(await createRouter({ config, logger }));\n http.addAuthPolicy({\n path: '/',\n allow: 'unauthenticated',\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","createRouter"],"mappings":";;;;;AAyBO,MAAM,qBAAqBA,oCAAA,CAAoB;AAAA,EACpD,QAAA,EAAU,cAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,MAAMC,6BAAA,CAAa,UAAA;AAAA,QACnB,QAAQA,6BAAA,CAAa,UAAA;AAAA,QACrB,QAAQA,6BAAA,CAAa;AAAA,OACvB;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,QAAO,EAAG;AACnC,QAAA,IAAA,CAAK,IAAI,MAAMC,mBAAA,CAAa,EAAE,MAAA,EAAQ,MAAA,EAAQ,CAAC,CAAA;AAC/C,QAAA,IAAA,CAAK,aAAA,CAAc;AAAA,UACjB,IAAA,EAAM,GAAA;AAAA,UACN,KAAA,EAAO;AAAA,SACR,CAAA;AAAA,MACH;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var utils = require('../utils.cjs.js');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
10
|
+
var path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
11
|
+
|
|
12
|
+
class TranslationService {
|
|
13
|
+
constructor(config, logger) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.overridesFiles = this.config.getOptionalStringArray("i18n.overrides") ?? [];
|
|
17
|
+
this.configuredLocales = this.config.getOptionalStringArray(
|
|
18
|
+
"i18n.locales"
|
|
19
|
+
) ?? ["en"];
|
|
20
|
+
}
|
|
21
|
+
cachedTranslations = null;
|
|
22
|
+
overridesFiles;
|
|
23
|
+
configuredLocales;
|
|
24
|
+
/**
|
|
25
|
+
* Finds the topmost folder with the given name by searching up the directory tree
|
|
26
|
+
* Similar to the approach used in marketplace plugin
|
|
27
|
+
*/
|
|
28
|
+
findTopmostFolder(folderName, startPath = process.cwd()) {
|
|
29
|
+
let currentPath = path__default.default.resolve(startPath);
|
|
30
|
+
let topmostFoundPath = null;
|
|
31
|
+
while (currentPath !== path__default.default.parse(currentPath).root) {
|
|
32
|
+
const targetFolderPath2 = path__default.default.join(currentPath, folderName);
|
|
33
|
+
if (fs__default.default.existsSync(targetFolderPath2) && fs__default.default.statSync(targetFolderPath2).isDirectory()) {
|
|
34
|
+
topmostFoundPath = targetFolderPath2;
|
|
35
|
+
}
|
|
36
|
+
currentPath = path__default.default.dirname(currentPath);
|
|
37
|
+
}
|
|
38
|
+
const targetFolderPath = path__default.default.join(currentPath, folderName);
|
|
39
|
+
if (fs__default.default.existsSync(targetFolderPath) && fs__default.default.statSync(targetFolderPath).isDirectory()) {
|
|
40
|
+
topmostFoundPath = targetFolderPath;
|
|
41
|
+
}
|
|
42
|
+
return topmostFoundPath;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the internal translations directory path
|
|
46
|
+
* Uses dynamic folder search to find the topmost 'translations' folder
|
|
47
|
+
*/
|
|
48
|
+
resolveInternalTranslationsDirectory() {
|
|
49
|
+
const srcTranslationsPath = this.findTopmostFolder("src");
|
|
50
|
+
if (srcTranslationsPath) {
|
|
51
|
+
const translationsPath2 = path__default.default.join(srcTranslationsPath, "translations");
|
|
52
|
+
if (fs__default.default.existsSync(translationsPath2) && fs__default.default.statSync(translationsPath2).isDirectory()) {
|
|
53
|
+
this.logger.info(
|
|
54
|
+
`Found internal translations directory: ${translationsPath2}`
|
|
55
|
+
);
|
|
56
|
+
return translationsPath2;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const translationsPath = this.findTopmostFolder("translations");
|
|
60
|
+
if (translationsPath) {
|
|
61
|
+
this.logger.info(
|
|
62
|
+
`Found internal translations directory: ${translationsPath}`
|
|
63
|
+
);
|
|
64
|
+
return translationsPath;
|
|
65
|
+
}
|
|
66
|
+
this.logger.debug(
|
|
67
|
+
"No internal translations directory found in any expected location"
|
|
68
|
+
);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Scans a directory for JSON translation files
|
|
73
|
+
*/
|
|
74
|
+
scanDirectoryForTranslationFiles(directoryPath) {
|
|
75
|
+
try {
|
|
76
|
+
if (!fs__default.default.existsSync(directoryPath)) {
|
|
77
|
+
this.logger.warn(
|
|
78
|
+
`Internal translation directory not found: ${directoryPath}`
|
|
79
|
+
);
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const files = fs__default.default.readdirSync(directoryPath);
|
|
83
|
+
const jsonFiles = files.filter((file) => file.endsWith(".json")).map((file) => path__default.default.join(directoryPath, file));
|
|
84
|
+
if (jsonFiles.length > 0) {
|
|
85
|
+
this.logger.info(
|
|
86
|
+
`Found ${jsonFiles.length} translation files in internal directory: ${directoryPath}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return jsonFiles;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
this.logger.warn(
|
|
92
|
+
`Failed to scan internal directory ${directoryPath}: ${error}`
|
|
93
|
+
);
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Processes translation files and merges them into the target object
|
|
99
|
+
*/
|
|
100
|
+
processTranslationFiles(files, mergedTranslations, source) {
|
|
101
|
+
let filesProcessed = 0;
|
|
102
|
+
let filesNotFound = 0;
|
|
103
|
+
let filesInvalid = 0;
|
|
104
|
+
for (const file of files) {
|
|
105
|
+
try {
|
|
106
|
+
if (!fs__default.default.existsSync(file)) {
|
|
107
|
+
this.logger.warn(`Translation file not found: ${file} (${source})`);
|
|
108
|
+
filesNotFound++;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const raw = fs__default.default.readFileSync(file, "utf8");
|
|
112
|
+
const json = JSON.parse(raw);
|
|
113
|
+
if (!utils.isValidJSONTranslation(json)) {
|
|
114
|
+
this.logger.warn(
|
|
115
|
+
`Invalid JSON translation file format: ${file} (${source})`
|
|
116
|
+
);
|
|
117
|
+
filesInvalid++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
utils.deepMergeTranslations(mergedTranslations, json);
|
|
121
|
+
this.logger.debug(`Processed translation file: ${file} (${source})`);
|
|
122
|
+
filesProcessed++;
|
|
123
|
+
} catch (parseError) {
|
|
124
|
+
this.logger.warn(
|
|
125
|
+
`Failed to parse JSON from file: ${file} (${source}) - ${parseError}`
|
|
126
|
+
);
|
|
127
|
+
filesInvalid++;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { filesProcessed, filesNotFound, filesInvalid };
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Gets all translations, processing both internal directory and config overrides
|
|
134
|
+
*/
|
|
135
|
+
async getTranslations() {
|
|
136
|
+
if (this.cachedTranslations) {
|
|
137
|
+
return {
|
|
138
|
+
translations: this.cachedTranslations,
|
|
139
|
+
stats: {
|
|
140
|
+
totalFilesProcessed: 0,
|
|
141
|
+
totalFilesNotFound: 0,
|
|
142
|
+
totalFilesInvalid: 0
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const mergedTranslations = {};
|
|
147
|
+
let totalFilesProcessed = 0;
|
|
148
|
+
let totalFilesNotFound = 0;
|
|
149
|
+
let totalFilesInvalid = 0;
|
|
150
|
+
let internalFiles = [];
|
|
151
|
+
const internalDirectory = this.resolveInternalTranslationsDirectory();
|
|
152
|
+
if (internalDirectory) {
|
|
153
|
+
internalFiles = this.scanDirectoryForTranslationFiles(internalDirectory);
|
|
154
|
+
if (internalFiles.length > 0) {
|
|
155
|
+
const internalStats = this.processTranslationFiles(
|
|
156
|
+
internalFiles,
|
|
157
|
+
mergedTranslations,
|
|
158
|
+
"internal directory"
|
|
159
|
+
);
|
|
160
|
+
totalFilesProcessed += internalStats.filesProcessed;
|
|
161
|
+
totalFilesNotFound += internalStats.filesNotFound;
|
|
162
|
+
totalFilesInvalid += internalStats.filesInvalid;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (this.overridesFiles.length > 0) {
|
|
166
|
+
const overrideStats = this.processTranslationFiles(
|
|
167
|
+
this.overridesFiles,
|
|
168
|
+
mergedTranslations,
|
|
169
|
+
"config overrides"
|
|
170
|
+
);
|
|
171
|
+
totalFilesProcessed += overrideStats.filesProcessed;
|
|
172
|
+
totalFilesNotFound += overrideStats.filesNotFound;
|
|
173
|
+
totalFilesInvalid += overrideStats.filesInvalid;
|
|
174
|
+
}
|
|
175
|
+
const totalFilesConfigured = internalFiles.length + this.overridesFiles.length;
|
|
176
|
+
if (totalFilesConfigured === 0) {
|
|
177
|
+
this.logger.info(
|
|
178
|
+
"No translation files found (neither auto-detected internal directory nor config overrides)"
|
|
179
|
+
);
|
|
180
|
+
this.cachedTranslations = {};
|
|
181
|
+
return {
|
|
182
|
+
translations: {},
|
|
183
|
+
stats: {
|
|
184
|
+
totalFilesProcessed: 0,
|
|
185
|
+
totalFilesNotFound: 0,
|
|
186
|
+
totalFilesInvalid: 0
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (totalFilesNotFound === totalFilesConfigured) {
|
|
191
|
+
this.logger.warn("All configured translation files were not found");
|
|
192
|
+
throw new Error("All configured translation files were not found");
|
|
193
|
+
}
|
|
194
|
+
if (totalFilesProcessed === 0) {
|
|
195
|
+
this.logger.warn(
|
|
196
|
+
"No valid translation files found in the provided files"
|
|
197
|
+
);
|
|
198
|
+
throw new Error("No valid translation files found in the provided files");
|
|
199
|
+
}
|
|
200
|
+
this.cachedTranslations = utils.filterLocales(
|
|
201
|
+
mergedTranslations,
|
|
202
|
+
this.configuredLocales
|
|
203
|
+
);
|
|
204
|
+
this.logger.info(
|
|
205
|
+
`Translation processing complete: ${totalFilesProcessed} files processed, ${totalFilesNotFound} files not found, ${totalFilesInvalid} files invalid`
|
|
206
|
+
);
|
|
207
|
+
return {
|
|
208
|
+
translations: this.cachedTranslations,
|
|
209
|
+
stats: {
|
|
210
|
+
totalFilesProcessed,
|
|
211
|
+
totalFilesNotFound,
|
|
212
|
+
totalFilesInvalid
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Clears the cached translations (useful for testing or when files change)
|
|
218
|
+
*/
|
|
219
|
+
clearCache() {
|
|
220
|
+
this.cachedTranslations = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
exports.TranslationService = TranslationService;
|
|
225
|
+
//# sourceMappingURL=TranslationService.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TranslationService.cjs.js","sources":["../../src/service/TranslationService.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport fs from 'fs';\nimport path from 'path';\n\nimport {\n deepMergeTranslations,\n filterLocales,\n isValidJSONTranslation,\n} from '../utils';\n\nexport interface TranslationFileStats {\n filesProcessed: number;\n filesNotFound: number;\n filesInvalid: number;\n}\n\nexport interface TranslationResult {\n translations: Record<string, any>;\n stats: {\n totalFilesProcessed: number;\n totalFilesNotFound: number;\n totalFilesInvalid: number;\n };\n}\n\n/**\n * Service for managing translation files and processing\n */\nexport class TranslationService {\n private cachedTranslations: Record<string, any> | null = null;\n private readonly overridesFiles: string[];\n private readonly configuredLocales: string[];\n\n constructor(\n private readonly config: Config,\n private readonly logger: LoggerService,\n ) {\n this.overridesFiles =\n this.config.getOptionalStringArray('i18n.overrides') ?? [];\n this.configuredLocales = this.config.getOptionalStringArray(\n 'i18n.locales',\n ) ?? ['en'];\n }\n\n /**\n * Finds the topmost folder with the given name by searching up the directory tree\n * Similar to the approach used in marketplace plugin\n */\n private findTopmostFolder(\n folderName: string,\n startPath = process.cwd(),\n ): string | null {\n let currentPath = path.resolve(startPath);\n let topmostFoundPath: string | null = null;\n\n while (currentPath !== path.parse(currentPath).root) {\n const targetFolderPath = path.join(currentPath, folderName);\n if (\n fs.existsSync(targetFolderPath) &&\n fs.statSync(targetFolderPath).isDirectory()\n ) {\n topmostFoundPath = targetFolderPath;\n }\n currentPath = path.dirname(currentPath);\n }\n const targetFolderPath = path.join(currentPath, folderName);\n\n if (\n fs.existsSync(targetFolderPath) &&\n fs.statSync(targetFolderPath).isDirectory()\n ) {\n topmostFoundPath = targetFolderPath;\n }\n\n return topmostFoundPath;\n }\n\n /**\n * Resolves the internal translations directory path\n * Uses dynamic folder search to find the topmost 'translations' folder\n */\n private resolveInternalTranslationsDirectory(): string | null {\n // First try to find 'src/translations' in the topmost directory structure\n const srcTranslationsPath = this.findTopmostFolder('src');\n if (srcTranslationsPath) {\n const translationsPath = path.join(srcTranslationsPath, 'translations');\n if (\n fs.existsSync(translationsPath) &&\n fs.statSync(translationsPath).isDirectory()\n ) {\n this.logger.info(\n `Found internal translations directory: ${translationsPath}`,\n );\n return translationsPath;\n }\n }\n\n // Fallback: look for 'translations' folder directly\n const translationsPath = this.findTopmostFolder('translations');\n if (translationsPath) {\n this.logger.info(\n `Found internal translations directory: ${translationsPath}`,\n );\n return translationsPath;\n }\n\n this.logger.debug(\n 'No internal translations directory found in any expected location',\n );\n return null;\n }\n\n /**\n * Scans a directory for JSON translation files\n */\n private scanDirectoryForTranslationFiles(directoryPath: string): string[] {\n try {\n if (!fs.existsSync(directoryPath)) {\n this.logger.warn(\n `Internal translation directory not found: ${directoryPath}`,\n );\n return [];\n }\n\n const files = fs.readdirSync(directoryPath);\n const jsonFiles = files\n .filter(file => file.endsWith('.json'))\n .map(file => path.join(directoryPath, file));\n\n if (jsonFiles.length > 0) {\n this.logger.info(\n `Found ${jsonFiles.length} translation files in internal directory: ${directoryPath}`,\n );\n }\n\n return jsonFiles;\n } catch (error) {\n this.logger.warn(\n `Failed to scan internal directory ${directoryPath}: ${error}`,\n );\n return [];\n }\n }\n\n /**\n * Processes translation files and merges them into the target object\n */\n private processTranslationFiles(\n files: string[],\n mergedTranslations: Record<string, any>,\n source: string,\n ): TranslationFileStats {\n let filesProcessed = 0;\n let filesNotFound = 0;\n let filesInvalid = 0;\n\n for (const file of files) {\n try {\n if (!fs.existsSync(file)) {\n this.logger.warn(`Translation file not found: ${file} (${source})`);\n filesNotFound++;\n continue;\n }\n\n const raw = fs.readFileSync(file, 'utf8');\n const json = JSON.parse(raw);\n if (!isValidJSONTranslation(json)) {\n this.logger.warn(\n `Invalid JSON translation file format: ${file} (${source})`,\n );\n filesInvalid++;\n continue;\n }\n\n deepMergeTranslations(mergedTranslations, json);\n this.logger.debug(`Processed translation file: ${file} (${source})`);\n filesProcessed++;\n } catch (parseError) {\n this.logger.warn(\n `Failed to parse JSON from file: ${file} (${source}) - ${parseError}`,\n );\n filesInvalid++;\n }\n }\n\n return { filesProcessed, filesNotFound, filesInvalid };\n }\n\n /**\n * Gets all translations, processing both internal directory and config overrides\n */\n async getTranslations(): Promise<TranslationResult> {\n if (this.cachedTranslations) {\n return {\n translations: this.cachedTranslations,\n stats: {\n totalFilesProcessed: 0,\n totalFilesNotFound: 0,\n totalFilesInvalid: 0,\n },\n };\n }\n\n const mergedTranslations: Record<string, any> = {};\n let totalFilesProcessed = 0;\n let totalFilesNotFound = 0;\n let totalFilesInvalid = 0;\n\n // Step 1: Process internal directory files first (auto-detected)\n let internalFiles: string[] = [];\n const internalDirectory = this.resolveInternalTranslationsDirectory();\n if (internalDirectory) {\n internalFiles = this.scanDirectoryForTranslationFiles(internalDirectory);\n if (internalFiles.length > 0) {\n const internalStats = this.processTranslationFiles(\n internalFiles,\n mergedTranslations,\n 'internal directory',\n );\n totalFilesProcessed += internalStats.filesProcessed;\n totalFilesNotFound += internalStats.filesNotFound;\n totalFilesInvalid += internalStats.filesInvalid;\n }\n }\n\n // Step 2: Process config override files (these will override internal files)\n if (this.overridesFiles.length > 0) {\n const overrideStats = this.processTranslationFiles(\n this.overridesFiles,\n mergedTranslations,\n 'config overrides',\n );\n totalFilesProcessed += overrideStats.filesProcessed;\n totalFilesNotFound += overrideStats.filesNotFound;\n totalFilesInvalid += overrideStats.filesInvalid;\n }\n\n // Determine if we have any files configured\n const totalFilesConfigured =\n internalFiles.length + this.overridesFiles.length;\n\n // If no files were configured, return empty response\n if (totalFilesConfigured === 0) {\n this.logger.info(\n 'No translation files found (neither auto-detected internal directory nor config overrides)',\n );\n this.cachedTranslations = {};\n return {\n translations: {},\n stats: {\n totalFilesProcessed: 0,\n totalFilesNotFound: 0,\n totalFilesInvalid: 0,\n },\n };\n }\n\n // If files were configured but none were found, throw error\n if (totalFilesNotFound === totalFilesConfigured) {\n this.logger.warn('All configured translation files were not found');\n throw new Error('All configured translation files were not found');\n }\n\n // If files were found but none contained valid translations, throw error\n if (totalFilesProcessed === 0) {\n this.logger.warn(\n 'No valid translation files found in the provided files',\n );\n throw new Error('No valid translation files found in the provided files');\n }\n\n // Filter translations by configured locales\n this.cachedTranslations = filterLocales(\n mergedTranslations,\n this.configuredLocales,\n );\n\n // Log summary\n this.logger.info(\n `Translation processing complete: ${totalFilesProcessed} files processed, ${totalFilesNotFound} files not found, ${totalFilesInvalid} files invalid`,\n );\n\n return {\n translations: this.cachedTranslations,\n stats: {\n totalFilesProcessed,\n totalFilesNotFound,\n totalFilesInvalid,\n },\n };\n }\n\n /**\n * Clears the cached translations (useful for testing or when files change)\n */\n clearCache(): void {\n this.cachedTranslations = null;\n }\n}\n"],"names":["path","targetFolderPath","fs","translationsPath","isValidJSONTranslation","deepMergeTranslations","filterLocales"],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var express = require('express');
|
|
4
|
+
var TranslationService = require('./TranslationService.cjs.js');
|
|
5
|
+
|
|
6
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var express__default = /*#__PURE__*/_interopDefaultCompat(express);
|
|
9
|
+
|
|
10
|
+
async function createRouter({
|
|
11
|
+
logger,
|
|
12
|
+
config
|
|
13
|
+
}) {
|
|
14
|
+
const translationService = new TranslationService.TranslationService(config, logger);
|
|
15
|
+
const router = express.Router();
|
|
16
|
+
router.use(express__default.default.json());
|
|
17
|
+
router.get("/", async (_, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const result = await translationService.getTranslations();
|
|
20
|
+
res.json(result.translations);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
23
|
+
if (errorMessage.includes("All configured translation files were not found")) {
|
|
24
|
+
res.status(404).json({ error: "All configured translation files were not found" });
|
|
25
|
+
} else if (errorMessage.includes(
|
|
26
|
+
"No valid translation files found in the provided files"
|
|
27
|
+
)) {
|
|
28
|
+
res.status(404).json({
|
|
29
|
+
error: "No valid translation files found in the provided files"
|
|
30
|
+
});
|
|
31
|
+
} else {
|
|
32
|
+
logger.error(`Failed to process translation files: ${error}`);
|
|
33
|
+
res.status(500).json({ error: "Failed to process translation files" });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return router;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exports.createRouter = createRouter;
|
|
41
|
+
//# sourceMappingURL=router.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport express, { Router } from 'express';\n\nimport { TranslationService } from './TranslationService';\n\n/**\n * @public\n *\n */\nexport interface RouterOptions {\n logger: LoggerService;\n config: Config;\n}\n/**\n * @public\n *\n */\nexport async function createRouter({\n logger,\n config,\n}: RouterOptions): Promise<Router> {\n const translationService = new TranslationService(config, logger);\n\n const router = Router();\n router.use(express.json());\n\n router.get('/', async (_, res) => {\n try {\n const result = await translationService.getTranslations();\n res.json(result.translations);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error';\n\n if (\n errorMessage.includes('All configured translation files were not found')\n ) {\n res\n .status(404)\n .json({ error: 'All configured translation files were not found' });\n } else if (\n errorMessage.includes(\n 'No valid translation files found in the provided files',\n )\n ) {\n res.status(404).json({\n error: 'No valid translation files found in the provided files',\n });\n } else {\n logger.error(`Failed to process translation files: ${error}`);\n res.status(500).json({ error: 'Failed to process translation files' });\n }\n }\n });\n\n return router;\n}\n"],"names":["TranslationService","Router","express"],"mappings":";;;;;;;;;AAiCA,eAAsB,YAAA,CAAa;AAAA,EACjC,MAAA;AAAA,EACA;AACF,CAAA,EAAmC;AACjC,EAAA,MAAM,kBAAA,GAAqB,IAAIA,qCAAA,CAAmB,MAAA,EAAQ,MAAM,CAAA;AAEhE,EAAA,MAAM,SAASC,cAAA,EAAO;AACtB,EAAA,MAAA,CAAO,GAAA,CAAIC,wBAAA,CAAQ,IAAA,EAAM,CAAA;AAEzB,EAAA,MAAA,CAAO,GAAA,CAAI,GAAA,EAAK,OAAO,CAAA,EAAG,GAAA,KAAQ;AAChC,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,kBAAA,CAAmB,eAAA,EAAgB;AACxD,MAAA,GAAA,CAAI,IAAA,CAAK,OAAO,YAAY,CAAA;AAAA,IAC9B,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,YAAA,GACJ,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,eAAA;AAE3C,MAAA,IACE,YAAA,CAAa,QAAA,CAAS,iDAAiD,CAAA,EACvE;AACA,QAAA,GAAA,CACG,OAAO,GAAG,CAAA,CACV,KAAK,EAAE,KAAA,EAAO,mDAAmD,CAAA;AAAA,MACtE,WACE,YAAA,CAAa,QAAA;AAAA,QACX;AAAA,OACF,EACA;AACA,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK;AAAA,UACnB,KAAA,EAAO;AAAA,SACR,CAAA;AAAA,MACH,CAAA,MAAO;AACL,QAAA,MAAA,CAAO,KAAA,CAAM,CAAA,qCAAA,EAAwC,KAAK,CAAA,CAAE,CAAA;AAC5D,QAAA,GAAA,CAAI,OAAO,GAAG,CAAA,CAAE,KAAK,EAAE,KAAA,EAAO,uCAAuC,CAAA;AAAA,MACvE;AAAA,IACF;AAAA,EACF,CAAC,CAAA;AAED,EAAA,OAAO,MAAA;AACT;;;;"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function isValidJSONTranslation(json) {
|
|
4
|
+
if (typeof json !== "object" || json === null) return false;
|
|
5
|
+
for (const [pluginRef, locales] of Object.entries(json)) {
|
|
6
|
+
if (typeof pluginRef !== "string") return false;
|
|
7
|
+
if (typeof locales !== "object" || locales === null) return false;
|
|
8
|
+
for (const [locale, messages] of Object.entries(locales)) {
|
|
9
|
+
if (typeof locale !== "string") return false;
|
|
10
|
+
if (typeof messages !== "object" || messages === null) return false;
|
|
11
|
+
for (const [k, v] of Object.entries(messages)) {
|
|
12
|
+
if (typeof k !== "string" || typeof v !== "string") {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
function deepMergeTranslations(target, source) {
|
|
21
|
+
for (const key of Object.keys(source)) {
|
|
22
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
23
|
+
target[key] = deepMergeTranslations(target[key] || {}, source[key]);
|
|
24
|
+
} else {
|
|
25
|
+
target[key] = source[key];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return target;
|
|
29
|
+
}
|
|
30
|
+
function filterLocales(allTranslations, configuredLocales) {
|
|
31
|
+
const filtered = {};
|
|
32
|
+
for (const pluginId of Object.keys(allTranslations)) {
|
|
33
|
+
for (const locale of configuredLocales) {
|
|
34
|
+
if (allTranslations[pluginId][locale]) {
|
|
35
|
+
filtered[pluginId] = {
|
|
36
|
+
...filtered[pluginId] ?? {},
|
|
37
|
+
[locale]: allTranslations[pluginId][locale]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return filtered;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
exports.deepMergeTranslations = deepMergeTranslations;
|
|
46
|
+
exports.filterLocales = filterLocales;
|
|
47
|
+
exports.isValidJSONTranslation = isValidJSONTranslation;
|
|
48
|
+
//# sourceMappingURL=utils.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.cjs.js","sources":["../src/utils.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport function isValidJSONTranslation(json: Record<string, any>): boolean {\n if (typeof json !== 'object' || json === null) return false;\n\n for (const [pluginRef, locales] of Object.entries(json)) {\n if (typeof pluginRef !== 'string') return false;\n if (typeof locales !== 'object' || locales === null) return false;\n\n for (const [locale, messages] of Object.entries(locales)) {\n if (typeof locale !== 'string') return false;\n if (typeof messages !== 'object' || messages === null) return false;\n\n for (const [k, v] of Object.entries(messages)) {\n if (typeof k !== 'string' || typeof v !== 'string') {\n return false;\n }\n }\n }\n }\n\n return true;\n}\n\nexport function deepMergeTranslations(\n target: Record<string, any>,\n source: Record<string, any>,\n): Record<string, any> {\n for (const key of Object.keys(source)) {\n if (\n source[key] &&\n typeof source[key] === 'object' &&\n !Array.isArray(source[key])\n ) {\n target[key] = deepMergeTranslations(target[key] || {}, source[key]);\n } else {\n target[key] = source[key];\n }\n }\n return target;\n}\n\nexport function filterLocales(\n allTranslations: Record<string, any>,\n configuredLocales: string[],\n): Record<string, any> {\n const filtered: Record<string, any> = {};\n for (const pluginId of Object.keys(allTranslations)) {\n for (const locale of configuredLocales) {\n if (allTranslations[pluginId][locale]) {\n filtered[pluginId] = {\n ...(filtered[pluginId] ?? {}),\n [locale]: allTranslations[pluginId][locale],\n };\n }\n }\n }\n\n return filtered;\n}\n"],"names":[],"mappings":";;AAeO,SAAS,uBAAuB,IAAA,EAAoC;AACzE,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,IAAA,KAAS,MAAM,OAAO,KAAA;AAEtD,EAAA,KAAA,MAAW,CAAC,SAAA,EAAW,OAAO,KAAK,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,EAAG;AACvD,IAAA,IAAI,OAAO,SAAA,KAAc,QAAA,EAAU,OAAO,KAAA;AAC1C,IAAA,IAAI,OAAO,OAAA,KAAY,QAAA,IAAY,OAAA,KAAY,MAAM,OAAO,KAAA;AAE5D,IAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,QAAQ,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,EAAG;AACxD,MAAA,IAAI,OAAO,MAAA,KAAW,QAAA,EAAU,OAAO,KAAA;AACvC,MAAA,IAAI,OAAO,QAAA,KAAa,QAAA,IAAY,QAAA,KAAa,MAAM,OAAO,KAAA;AAE9D,MAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAC7C,QAAA,IAAI,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,MAAM,QAAA,EAAU;AAClD,UAAA,OAAO,KAAA;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,qBAAA,CACd,QACA,MAAA,EACqB;AACrB,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,EAAG;AACrC,IAAA,IACE,MAAA,CAAO,GAAG,CAAA,IACV,OAAO,OAAO,GAAG,CAAA,KAAM,QAAA,IACvB,CAAC,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,GAAG,CAAC,CAAA,EAC1B;AACA,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,qBAAA,CAAsB,MAAA,CAAO,GAAG,KAAK,EAAC,EAAG,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,IACpE,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,MAAA,CAAO,GAAG,CAAA;AAAA,IAC1B;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,aAAA,CACd,iBACA,iBAAA,EACqB;AACrB,EAAA,MAAM,WAAgC,EAAC;AACvC,EAAA,KAAA,MAAW,QAAA,IAAY,MAAA,CAAO,IAAA,CAAK,eAAe,CAAA,EAAG;AACnD,IAAA,KAAA,MAAW,UAAU,iBAAA,EAAmB;AACtC,MAAA,IAAI,eAAA,CAAgB,QAAQ,CAAA,CAAE,MAAM,CAAA,EAAG;AACrC,QAAA,QAAA,CAAS,QAAQ,CAAA,GAAI;AAAA,UACnB,GAAI,QAAA,CAAS,QAAQ,CAAA,IAAK,EAAC;AAAA,UAC3B,CAAC,MAAM,GAAG,eAAA,CAAgB,QAAQ,EAAE,MAAM;AAAA,SAC5C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,QAAA;AACT;;;;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@red-hat-developer-hub/backstage-plugin-translations-backend",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"main": "dist/index.cjs.js",
|
|
10
|
+
"types": "dist/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/redhat-developer/rhdh-plugins",
|
|
15
|
+
"directory": "workspaces/translations/plugins/translations-backend"
|
|
16
|
+
},
|
|
17
|
+
"backstage": {
|
|
18
|
+
"role": "backend-plugin",
|
|
19
|
+
"pluginId": "translations",
|
|
20
|
+
"pluginPackages": [
|
|
21
|
+
"@red-hat-developer-hub/backstage-plugin-translations",
|
|
22
|
+
"@red-hat-developer-hub/backstage-plugin-translations-backend"
|
|
23
|
+
],
|
|
24
|
+
"features": {
|
|
25
|
+
".": "@backstage/BackendFeature"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"start": "backstage-cli package start",
|
|
30
|
+
"build": "backstage-cli package build",
|
|
31
|
+
"lint": "backstage-cli package lint",
|
|
32
|
+
"test": "backstage-cli package test",
|
|
33
|
+
"clean": "backstage-cli package clean",
|
|
34
|
+
"prepack": "backstage-cli package prepack",
|
|
35
|
+
"postpack": "backstage-cli package postpack"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@backstage/backend-defaults": "^0.12.0",
|
|
39
|
+
"@backstage/backend-plugin-api": "^1.4.2",
|
|
40
|
+
"@backstage/catalog-client": "^1.11.0",
|
|
41
|
+
"@backstage/config": "^1.3.3",
|
|
42
|
+
"@backstage/errors": "^1.2.7",
|
|
43
|
+
"@backstage/plugin-catalog-node": "^1.18.0",
|
|
44
|
+
"express": "4.21.2",
|
|
45
|
+
"express-promise-router": "^4.1.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@backstage/backend-test-utils": "^1.8.0",
|
|
49
|
+
"@backstage/cli": "^0.34.1",
|
|
50
|
+
"@types/express": "^4.17.6",
|
|
51
|
+
"@types/supertest": "^2.0.12",
|
|
52
|
+
"supertest": "^6.2.4"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist"
|
|
56
|
+
],
|
|
57
|
+
"typesVersions": {
|
|
58
|
+
"*": {
|
|
59
|
+
"package.json": [
|
|
60
|
+
"package.json"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|