i18next-mcp-server
Version:
A comprehensive Model Context Protocol (MCP) server for i18next translation management, health checking, and automated translation workflows
151 lines • 5.95 kB
JavaScript
import path from 'node:path';
import fs from 'node:fs/promises';
import { I18nConfigSchema, ConfigurationError } from '../types/index.js';
export class ConfigManager {
config = null;
async loadConfig(configPath) {
let config = {};
// Try to load config file if provided
if (configPath) {
try {
const configContent = await fs.readFile(configPath, 'utf-8');
config = JSON.parse(configContent);
}
catch (error) {
throw new ConfigurationError(`Failed to load config from ${configPath}`, { error });
}
}
// Apply environment variables and defaults
const fullConfig = {
projectRoot: process.env.I18N_PROJECT_ROOT || process.cwd(),
localesPath: process.env.I18N_LOCALES_DIR || process.env.I18N_LOCALES_PATH || config.localesPath || 'public/locales',
languages: process.env.I18N_LANGUAGES?.split(',') || config.languages || ['en'],
namespaces: process.env.I18N_NAMESPACES?.split(',') || config.namespaces || ['translation'],
defaultLanguage: process.env.I18N_DEFAULT_LANGUAGE || config.defaultLanguage || 'en',
defaultNamespace: config.defaultNamespace || 'translation',
keySeparator: config.keySeparator || '.',
nsSeparator: config.nsSeparator || ':',
interpolation: {
prefix: config.interpolation?.prefix || '{{',
suffix: config.interpolation?.suffix || '}}'
},
backup: {
enabled: config.backup?.enabled ?? true,
path: config.backup?.path || '.backups/i18n'
}
};
// Validate the configuration
try {
this.config = I18nConfigSchema.parse(fullConfig);
}
catch (error) {
throw new ConfigurationError('Invalid configuration', { error });
}
// Ensure paths are absolute
this.config.projectRoot = path.resolve(this.config.projectRoot);
this.config.localesPath = path.resolve(this.config.projectRoot, this.config.localesPath);
this.config.backup.path = path.resolve(this.config.projectRoot, this.config.backup.path);
// Check if the locales directory exists, create it if it doesn't
try {
await fs.access(this.config.localesPath);
}
catch {
console.warn(`⚠️ Locales directory not found: ${this.config.localesPath}`);
console.warn(` The directory will be created when needed.`);
console.warn(` You can specify a different path using I18N_LOCALES_DIR environment variable.`);
}
return this.config;
}
getConfig() {
if (!this.config) {
throw new ConfigurationError('Configuration not loaded. Call loadConfig() first.');
}
return this.config;
}
getAbsoluteLocalesPath() {
return this.getConfig().localesPath;
}
getLanguageFilePath(language, namespace) {
const config = this.getConfig();
return path.join(config.localesPath, language, `${namespace}.json`);
}
getAllLanguageFiles() {
const config = this.getConfig();
const files = [];
for (const language of config.languages) {
for (const namespace of config.namespaces) {
files.push({
language,
namespace,
path: this.getLanguageFilePath(language, namespace)
});
}
}
return files;
}
isValidLanguage(language) {
return this.getConfig().languages.includes(language);
}
isValidNamespace(namespace) {
return this.getConfig().namespaces.includes(namespace);
}
async createBackupPath() {
const config = this.getConfig();
if (!config.backup.enabled) {
throw new ConfigurationError('Backup is disabled in configuration');
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupDir = path.join(config.backup.path, timestamp);
try {
await fs.mkdir(backupDir, { recursive: true });
return backupDir;
}
catch (error) {
throw new ConfigurationError(`Failed to create backup directory: ${backupDir}`, { error });
}
}
async validateProject() {
const config = this.getConfig();
const issues = [];
// Check if project root exists
try {
await fs.access(config.projectRoot);
}
catch {
issues.push(`Project root does not exist: ${config.projectRoot}`);
}
// Check if locales directory exists
try {
await fs.access(config.localesPath);
}
catch {
issues.push(`Locales directory does not exist: ${config.localesPath}`);
}
// Check if language directories exist
for (const language of config.languages) {
const langDir = path.join(config.localesPath, language);
try {
await fs.access(langDir);
}
catch {
issues.push(`Language directory does not exist: ${langDir}`);
}
}
// Check for required namespace files in default language
const defaultLangDir = path.join(config.localesPath, config.defaultLanguage);
for (const namespace of config.namespaces) {
const filePath = path.join(defaultLangDir, `${namespace}.json`);
try {
await fs.access(filePath);
}
catch {
issues.push(`Required namespace file missing: ${filePath}`);
}
}
return {
valid: issues.length === 0,
issues
};
}
}
//# sourceMappingURL=config.js.map