@red-hat-developer-hub/backstage-plugin-translations-backend 0.2.0 → 0.2.1
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
CHANGED
|
@@ -23,7 +23,7 @@ class TranslationService {
|
|
|
23
23
|
configuredLocales;
|
|
24
24
|
/**
|
|
25
25
|
* Finds the topmost folder with the given name by searching up the directory tree
|
|
26
|
-
* Similar to the approach used in
|
|
26
|
+
* Similar to the approach used in extensions plugin
|
|
27
27
|
*/
|
|
28
28
|
findTopmostFolder(folderName, startPath = process.cwd()) {
|
|
29
29
|
let currentPath = path__default.default.resolve(startPath);
|
|
@@ -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 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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"}
|
|
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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"}
|