@memory-bank/mcp
Version:
Memory-enabled Co-Pilot (MCP) server for managing project documentation and context
280 lines • 11 kB
JavaScript
/**
* File-based implementation of the I18n Repository
*
* Stores and retrieves translations from JSON files.
* Each language has its own JSON file with all translations for that language.
*/
import path from 'path';
import fs from 'fs/promises';
import { logger } from '../../shared/utils/logger.js'; // Import logger
import { Language } from '../../domain/i18n/Language.js';
import { Translation } from '../../domain/i18n/Translation.js';
/**
* File-based implementation of I18n Repository
*/
export class FileI18nRepository {
componentLogger = logger.withContext({ component: 'FileI18nRepository' }); // Add logger instance
basePath;
translationCache;
cacheDirty;
/**
* Constructor
*
* @param basePath Base path for storing translation files
*/
constructor(basePath) {
this.basePath = basePath;
this.translationCache = new Map();
this.cacheDirty = true;
}
/**
* Initialize the repository
*
* @returns Promise resolving when initialization is complete
*/
async initialize() {
try {
// this.componentLogger.debug(`Initializing with basePath: ${this.basePath}`); // Removed debug log
// Directory creation is handled externally or assumed to exist.
// await fs.mkdir(this.basePath, { recursive: true }); // Removed directory creation
await this.loadAllTranslations();
// this.componentLogger.debug('Initialization complete.'); // Removed debug log
}
catch (error) {
throw new Error(`Failed to initialize I18n repository: ${error.message}`);
}
}
/**
* Gets a translation for a specific key and language
*
* @param key Translation key
* @param language Language to get translation for
* @returns Promise resolving to Translation or null if not found
*/
async getTranslation(key, language) {
if (this.cacheDirty) {
await this.loadAllTranslations();
}
const langCache = this.translationCache.get(language.code);
if (!langCache || !langCache.has(key)) {
return null;
}
const translationText = langCache.get(key);
if (!translationText) {
return null;
}
return new Translation(language, key, translationText);
}
/**
* Gets translations for a key in all supported languages
*
* @param key Translation key
* @returns Promise resolving to array of Translation objects
*/
async getTranslationsForKey(key) {
if (this.cacheDirty) {
await this.loadAllTranslations();
}
const translations = [];
for (const [langCode, langCache] of this.translationCache.entries()) {
if (langCache.has(key)) {
const translationText = langCache.get(key);
if (translationText) {
const language = new Language(langCode);
translations.push(new Translation(language, key, translationText));
}
}
}
return translations;
}
/**
* Gets all translations for a specific language
*
* @param language Language to get translations for
* @returns Promise resolving to array of Translation objects
*/
async getTranslationsForLanguage(language) {
if (this.cacheDirty) {
await this.loadAllTranslations();
}
const translations = [];
const langCache = this.translationCache.get(language.code);
if (langCache) {
for (const [key, text] of langCache.entries()) {
translations.push(new Translation(language, key, text));
}
}
return translations;
}
/**
* Saves a translation (creates or updates)
*
* @param translation Translation to save
* @returns Promise resolving to boolean indicating success
*/
async saveTranslation(translation) {
try {
if (this.cacheDirty) {
await this.loadAllTranslations();
}
const langCode = translation.language.code;
let langCache = this.translationCache.get(langCode);
if (!langCache) {
langCache = new Map();
this.translationCache.set(langCode, langCache);
}
langCache.set(translation.key, translation.text);
await this.saveTranslationsForLanguage(translation.language);
return true;
}
catch (error) {
this.componentLogger.error(`Failed to save translation: ${translation.key}`, { error }); // Use componentLogger
return false;
}
}
/**
* Checks if a translation exists for a specific key and language
*
* @param key Translation key
* @param language Language to check
* @returns Promise resolving to boolean indicating if translation exists
*/
async hasTranslation(key, language) {
if (this.cacheDirty) {
await this.loadAllTranslations();
}
const langCache = this.translationCache.get(language.code);
return !!langCache && langCache.has(key);
}
/**
* Gets a list of all translation keys
*
* @returns Promise resolving to array of translation keys
*/
async getAllKeys() {
if (this.cacheDirty) {
await this.loadAllTranslations();
}
const allKeys = new Set();
for (const langCache of this.translationCache.values()) {
for (const key of langCache.keys()) {
allKeys.add(key);
}
}
return Array.from(allKeys);
}
/**
* Gets a list of all supported languages
*
* @returns Promise resolving to array of Language objects
*/
async getSupportedLanguages() {
if (this.cacheDirty) {
await this.loadAllTranslations();
}
return Array.from(this.translationCache.keys())
.map(code => new Language(code));
} // End of getSupportedLanguages
// Private methods should be part of the class definition
/**
* Loads all translations into cache
*
* @private
*/
async loadAllTranslations() {
try {
// this.componentLogger.debug('Loading all translations...'); // Removed debug log
this.translationCache.clear();
try {
await fs.access(this.basePath);
}
catch (error) {
// If directory doesn't exist, log and return (don't create it here)
this.componentLogger.warn(`Translation directory not found: ${this.basePath}. No translations loaded.`);
// await fs.mkdir(this.basePath, { recursive: true }); // Removed directory creation
this.cacheDirty = false;
return;
}
const files = await fs.readdir(this.basePath);
const translationFiles = files.filter(file => file.endsWith('.json') &&
Language.supportedLanguages().some(lang => file === `${lang}.json`));
// this.componentLogger.debug(`Found ${translationFiles.length} translation files.`); // Removed debug log
for (const file of translationFiles) {
const langCode = path.basename(file, '.json');
await this.loadTranslationsForLanguage(langCode);
}
this.cacheDirty = false;
// this.componentLogger.debug('Finished loading all translations.'); // Removed debug log
}
catch (error) {
this.componentLogger.error('Failed to load translations', { error }); // Use componentLogger
throw new Error(`Failed to load translations: ${error.message}`);
}
} // End of loadAllTranslations
/**
* Loads translations for a specific language
*
* @param langCode Language code
* @private
*/
async loadTranslationsForLanguage(langCode) {
const filePath = path.join(this.basePath, `${langCode}.json`);
// this.componentLogger.debug(`Loading translations for ${langCode} from ${filePath}`); // Removed debug log
try {
try {
await fs.access(filePath);
}
catch (error) {
// this.componentLogger.warn(`Translation file not found for ${langCode}: ${filePath}`); // Removed debug log
return; // ファイルが存在しない場合は何もしない
}
const content = await fs.readFile(filePath, 'utf-8');
const data = JSON.parse(content);
if (data.language !== langCode) {
throw new Error(`Language mismatch in file ${filePath}: expected ${langCode}, got ${data.language}`);
}
const langCache = new Map();
for (const [key, text] of Object.entries(data.translations)) {
langCache.set(key, text);
}
this.translationCache.set(langCode, langCache);
// this.componentLogger.debug(`Loaded ${langCache.size} translations for ${langCode}`); // Removed debug log
}
catch (error) {
this.componentLogger.error(`Failed to load translations for ${langCode}`, { error }); // Use componentLogger
// エラーが発生しても処理を続行させるため、ここでは throw しない(キャッシュが空になるだけ)
// throw new Error(`Failed to load translations for ${langCode}: ${(error as Error).message}`);
}
} // End of loadTranslationsForLanguage
/**
* Saves translations for a specific language
*
* @param language Language
* @private
*/
async saveTranslationsForLanguage(language) {
try {
const langCode = language.code;
const langCache = this.translationCache.get(langCode);
if (!langCache) {
throw new Error(`No translations found for language ${langCode}`);
}
const translations = {};
for (const [key, text] of langCache.entries()) {
translations[key] = text;
}
const data = {
language: langCode,
translations,
lastModified: new Date().toISOString()
};
const filePath = path.join(this.basePath, `${langCode}.json`);
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
}
catch (error) {
this.componentLogger.error(`Failed to save translations for ${language.code}`, { error }); // Use componentLogger
throw new Error(`Failed to save translations for ${language.code}: ${error.message}`);
} // saveTranslationsForLanguage の閉じ括弧
}
}
//# sourceMappingURL=FileI18nRepository.js.map