@red-hat-developer-hub/backstage-plugin-translations-backend 0.5.0 → 0.6.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/CHANGELOG.md +8 -0
- package/README.md +50 -2
- package/config.schema.json +19 -0
- package/dist/service/TranslationService.cjs.js +23 -9
- package/dist/service/TranslationService.cjs.js.map +1 -1
- package/dist/utils.cjs.js +0 -15
- package/dist/utils.cjs.js.map +1 -1
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-translations-backend
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e515331: Serve the maintained JSON translations from the RHDH repository's `/translations` directory and merge them with mounted customer overrides in the NFS translation API. Expose the backend `i18n.overrides` configuration schema.
|
|
8
|
+
|
|
9
|
+
## 0.5.1
|
|
10
|
+
|
|
3
11
|
## 0.5.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Welcome to the translation backend plugin!
|
|
4
4
|
|
|
5
|
-
This plugin
|
|
5
|
+
This plugin serves JSON translation files from the RHDH repository's `/translations` directory and files mounted in the RHDH pod. It exposes `/api/translations`. Paths listed in `i18n.overrides` take highest priority. The result is cached until the backend restarts.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -26,7 +26,55 @@ backend.add(
|
|
|
26
26
|
## Getting started
|
|
27
27
|
|
|
28
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/
|
|
29
|
+
start-backend` in the root directory, and then navigating to [/api/translations](http://localhost:7007/api/translations).
|
|
30
|
+
|
|
31
|
+
## Configuring JSON overrides
|
|
32
|
+
|
|
33
|
+
The backend discovers valid JSON files in the repository-root `/translations`
|
|
34
|
+
directory, including files mounted there. Shipped catalogs are merged in source
|
|
35
|
+
order, followed by customer files with other basenames. Paths in
|
|
36
|
+
`i18n.overrides` are applied after all discovered files, in the order listed;
|
|
37
|
+
use this setting when a file must take priority over another discovered file.
|
|
38
|
+
|
|
39
|
+
```yaml title="app-config.yaml"
|
|
40
|
+
i18n:
|
|
41
|
+
overrides:
|
|
42
|
+
- /opt/app-root/src/translations/custom-overrides.json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
To add an override:
|
|
46
|
+
|
|
47
|
+
1. Create a JSON file whose top-level key is a translation reference ID, then
|
|
48
|
+
add the locale and messages you want to override. Message keys use the same
|
|
49
|
+
names as the translation reference.
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"plugin.homepage": {
|
|
54
|
+
"en": {
|
|
55
|
+
"header.welcome": "Welcome back! (custom override)"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
2. Place the file in the RHDH repository-root `translations/` directory for
|
|
62
|
+
local development, or mount it at `/opt/app-root/src/translations/` in a
|
|
63
|
+
deployment. Files in that directory are discovered automatically.
|
|
64
|
+
|
|
65
|
+
3. Add the file's path to `i18n.overrides` when it must override values from
|
|
66
|
+
other discovered files. Use the path as seen by the backend process; for a
|
|
67
|
+
ConfigMap mounted at the path above, configure
|
|
68
|
+
`/opt/app-root/src/translations/custom-overrides.json`.
|
|
69
|
+
|
|
70
|
+
4. Restart the backend after changing the file. The translation response is
|
|
71
|
+
cached until the backend restarts.
|
|
72
|
+
|
|
73
|
+
The repository demo config points to an explicit override in
|
|
74
|
+
`translations/app-config-overrides-demo.json`; its value takes precedence over
|
|
75
|
+
the automatically discovered demo file with the same key. In the local RHDH
|
|
76
|
+
workspace, the backend runs from `packages/backend`, so the configured path to
|
|
77
|
+
the root `translations/` directory starts with `../../translations/`.
|
|
30
78
|
|
|
31
79
|
## Development
|
|
32
80
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"i18n": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"overrides": {
|
|
9
|
+
"type": "array",
|
|
10
|
+
"items": {
|
|
11
|
+
"type": "string"
|
|
12
|
+
},
|
|
13
|
+
"description": "JSON files that override translations discovered in the `/translations` directory. Later files override earlier files.",
|
|
14
|
+
"visibility": "backend"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -14,15 +14,11 @@ class TranslationService {
|
|
|
14
14
|
this.config = config;
|
|
15
15
|
this.logger = logger;
|
|
16
16
|
this.overridesFiles = this.config.getOptionalStringArray("i18n.overrides") ?? [];
|
|
17
|
-
this.configuredLocales = this.config.getOptionalStringArray(
|
|
18
|
-
"i18n.locales"
|
|
19
|
-
) ?? ["en"];
|
|
20
17
|
}
|
|
21
18
|
config;
|
|
22
19
|
logger;
|
|
23
20
|
cachedTranslations = null;
|
|
24
21
|
overridesFiles;
|
|
25
|
-
configuredLocales;
|
|
26
22
|
/**
|
|
27
23
|
* Finds the topmost folder with the given name by searching up the directory tree
|
|
28
24
|
* Similar to the approach used in extensions plugin
|
|
@@ -70,6 +66,25 @@ class TranslationService {
|
|
|
70
66
|
);
|
|
71
67
|
return null;
|
|
72
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Orders catalog files so higher-priority sources override earlier sources.
|
|
71
|
+
*/
|
|
72
|
+
orderCatalogFiles(files) {
|
|
73
|
+
const sourceOrder = [
|
|
74
|
+
"backstage-",
|
|
75
|
+
"community-plugins-",
|
|
76
|
+
"rhdh-plugins-",
|
|
77
|
+
"rhdh-"
|
|
78
|
+
];
|
|
79
|
+
const priority = (file) => {
|
|
80
|
+
const name = path__default.default.basename(file);
|
|
81
|
+
const index = sourceOrder.findIndex((prefix) => name.startsWith(prefix));
|
|
82
|
+
return index === -1 ? sourceOrder.length : index;
|
|
83
|
+
};
|
|
84
|
+
return files.sort(
|
|
85
|
+
(a, b) => priority(a) - priority(b) || a.localeCompare(b)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
73
88
|
/**
|
|
74
89
|
* Scans a directory for JSON translation files
|
|
75
90
|
*/
|
|
@@ -152,7 +167,9 @@ class TranslationService {
|
|
|
152
167
|
let internalFiles = [];
|
|
153
168
|
const internalDirectory = this.resolveInternalTranslationsDirectory();
|
|
154
169
|
if (internalDirectory) {
|
|
155
|
-
internalFiles = this.
|
|
170
|
+
internalFiles = this.orderCatalogFiles(
|
|
171
|
+
this.scanDirectoryForTranslationFiles(internalDirectory)
|
|
172
|
+
);
|
|
156
173
|
if (internalFiles.length > 0) {
|
|
157
174
|
const internalStats = this.processTranslationFiles(
|
|
158
175
|
internalFiles,
|
|
@@ -199,10 +216,7 @@ class TranslationService {
|
|
|
199
216
|
);
|
|
200
217
|
throw new Error("No valid translation files found in the provided files");
|
|
201
218
|
}
|
|
202
|
-
this.cachedTranslations =
|
|
203
|
-
mergedTranslations,
|
|
204
|
-
this.configuredLocales
|
|
205
|
-
);
|
|
219
|
+
this.cachedTranslations = mergedTranslations;
|
|
206
220
|
this.logger.info(
|
|
207
221
|
`Translation processing complete: ${totalFilesProcessed} files processed, ${totalFilesNotFound} files not found, ${totalFilesInvalid} files invalid`
|
|
208
222
|
);
|
|
@@ -1 +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 extensions 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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"}
|
|
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 { deepMergeTranslations, isValidJSONTranslation } 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\n constructor(\n private readonly config: Config,\n private readonly logger: LoggerService,\n ) {\n this.overridesFiles =\n this.config.getOptionalStringArray('i18n.overrides') ?? [];\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 extensions 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 * Orders catalog files so higher-priority sources override earlier sources.\n */\n private orderCatalogFiles(files: string[]): string[] {\n const sourceOrder = [\n 'backstage-',\n 'community-plugins-',\n 'rhdh-plugins-',\n 'rhdh-',\n ];\n const priority = (file: string) => {\n const name = path.basename(file);\n const index = sourceOrder.findIndex(prefix => name.startsWith(prefix));\n // Unrecognized files are customer additions and should override shipped\n // catalogs when they define the same key.\n return index === -1 ? sourceOrder.length : index;\n };\n return files.sort(\n (a, b) => priority(a) - priority(b) || a.localeCompare(b),\n );\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: Load JSON files from the repository or mounted translations\n // directory.\n let internalFiles: string[] = [];\n const internalDirectory = this.resolveInternalTranslationsDirectory();\n if (internalDirectory) {\n internalFiles = this.orderCatalogFiles(\n this.scanDirectoryForTranslationFiles(internalDirectory),\n );\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: Explicit config overrides take highest priority.\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 this.cachedTranslations = mergedTranslations;\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"],"mappings":"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"}
|
package/dist/utils.cjs.js
CHANGED
|
@@ -27,22 +27,7 @@ function deepMergeTranslations(target, source) {
|
|
|
27
27
|
}
|
|
28
28
|
return target;
|
|
29
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
30
|
|
|
45
31
|
exports.deepMergeTranslations = deepMergeTranslations;
|
|
46
|
-
exports.filterLocales = filterLocales;
|
|
47
32
|
exports.isValidJSONTranslation = isValidJSONTranslation;
|
|
48
33
|
//# sourceMappingURL=utils.cjs.js.map
|
package/dist/utils.cjs.js.map
CHANGED
|
@@ -1 +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
|
|
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"],"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;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@red-hat-developer-hub/backstage-plugin-translations-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"main": "dist/index.cjs.js",
|
|
10
10
|
"types": "dist/index.d.ts"
|
|
11
11
|
},
|
|
12
|
+
"configSchema": "config.schema.json",
|
|
12
13
|
"repository": {
|
|
13
14
|
"type": "git",
|
|
14
15
|
"url": "https://github.com/redhat-developer/rhdh-plugins",
|
|
@@ -52,7 +53,8 @@
|
|
|
52
53
|
"supertest": "^6.2.4"
|
|
53
54
|
},
|
|
54
55
|
"files": [
|
|
55
|
-
"dist"
|
|
56
|
+
"dist",
|
|
57
|
+
"config.schema.json"
|
|
56
58
|
],
|
|
57
59
|
"typesVersions": {
|
|
58
60
|
"*": {
|