import axios from 'axios'; /** * I18n client for translation and localization * Used by other services to get translations and format values */ export class I18nClient { constructor(config = {}) { this.baseURL = config.baseURL || process.env.I18N_SERVICE_URL || 'http://localhost:3011'; this.timeout = config.timeout || 30000; this.apiVersion = config.apiVersion || 'v1'; this.defaultLanguage = config.defaultLanguage || 'en'; this.cache = new Map(); this.cacheTimeout = config.cacheTimeout || 300000; // 5 minutes this.client = axios.create({ baseURL: `${this.baseURL}/api/${this.apiVersion}`, timeout: this.timeout, headers: { 'Content-Type': 'application/json' } }); // Add request interceptor for internal service header this.client.interceptors.request.use(config => { config.headers['X-Internal-Service'] = process.env.SERVICE_NAME || 'unknown'; return config; }); } /** * Get single translation */ async translate(key, language = this.defaultLanguage, options = {}) { const cacheKey = `${language}:${key}:${options.namespace || 'translation'}`; // Check cache const cached = this.getFromCache(cacheKey); if (cached) return cached; try { const response = await this.client.get('/translations', { params: { key, language, namespace: options.namespace || 'translation' } }); const value = response.data.translation.value; this.setCache(cacheKey, value); return this.interpolate(value, options.variables || {}); } catch (error) { console.error('Translation error:', error); return options.defaultValue || key; } } /** * Get multiple translations */ async translateBatch(keys, language = this.defaultLanguage, options = {}) { try { const response = await this.client.get('/translations', { params: { keys: keys.join(','), language, namespace: options.namespace || 'translation' } }); return response.data.translations; } catch (error) { console.error('Batch translation error:', error); // Return keys as fallback return keys.reduce((acc, key) => { acc[key] = key; return acc; }, {}); } } /** * Detect user's language */ async detectLanguage(headers = {}) { try { const response = await this.client.get('/languages/detect', { headers: { 'accept-language': headers['accept-language'], 'cf-ipcountry': headers['cf-ipcountry'] } }); return response.data.language; } catch (error) { console.error('Language detection error:', error); return this.defaultLanguage; } } /** * Get enabled languages */ async getLanguages() { const cacheKey = 'enabled-languages'; const cached = this.getFromCache(cacheKey); if (cached) return cached; try { const response = await this.client.get('/languages/enabled'); const languages = response.data.languages; this.setCache(cacheKey, languages, 3600000); // Cache for 1 hour return languages; } catch (error) { console.error('Get languages error:', error); return []; } } /** * Format number */ async formatNumber(number, language = this.defaultLanguage, options = {}) { try { const response = await this.client.post('/localization/format/number', { number, language, options }); return response.data.formatted; } catch (error) { console.error('Number formatting error:', error); return number.toString(); } } /** * Format currency */ async formatCurrency(amount, language = this.defaultLanguage, currency = 'USD') { try { const response = await this.client.post('/localization/format/currency', { amount, language, currency }); return response.data.formatted; } catch (error) { console.error('Currency formatting error:', error); return `${currency} ${amount}`; } } /** * Format date */ async formatDate(date, language = this.defaultLanguage, options = {}) { try { const response = await this.client.post('/localization/format/date', { date, language, options }); return response.data.formatted; } catch (error) { console.error('Date formatting error:', error); return new Date(date).toLocaleString(); } } /** * Format relative time */ async formatRelativeTime(date, language = this.defaultLanguage) { try { const response = await this.client.post('/localization/format/relative-time', { date, language }); return response.data.formatted; } catch (error) { console.error('Relative time formatting error:', error); return date; } } /** * Get text direction */ async getTextDirection(language = this.defaultLanguage) { const cacheKey = `direction:${language}`; const cached = this.getFromCache(cacheKey); if (cached) return cached; try { const response = await this.client.get(`/localization/direction/${language}`); const direction = response.data.direction; this.setCache(cacheKey, direction, 3600000); // Cache for 1 hour return direction; } catch (error) { console.error('Get text direction error:', error); return 'ltr'; } } /** * Create i18n middleware for Express */ middleware() { return async (req, res, next) => { // Detect language req.language = await this.detectLanguage(req.headers); // Add translation function req.t = (key, options = {}) => { return this.translate(key, req.language, options); }; // Add formatting functions req.formatNumber = (number, options) => { return this.formatNumber(number, req.language, options); }; req.formatCurrency = (amount, currency) => { return this.formatCurrency(amount, req.language, currency); }; req.formatDate = (date, options) => { return this.formatDate(date, req.language, options); }; // Set response language header res.setHeader('Content-Language', req.language); next(); }; } /** * Interpolate variables in text */ interpolate(text, variables = {}) { return text.replace(/\{\{(\w+)\}\}/g, (match, key) => { return variables[key] !== undefined ? variables[key] : match; }); } /** * Cache management */ getFromCache(key) { const cached = this.cache.get(key); if (!cached) return null; if (Date.now() > cached.expires) { this.cache.delete(key); return null; } return cached.value; } setCache(key, value, timeout = this.cacheTimeout) { this.cache.set(key, { value, expires: Date.now() + timeout }); } clearCache() { this.cache.clear(); } } // Singleton instance let defaultClient = null; export const getI18nClient = (config) => { if (!defaultClient) { defaultClient = new I18nClient(config); } return defaultClient; }; // Direct exports for convenience export const translate = async (key, language, options) => { const client = getI18nClient(); return client.translate(key, language, options); }; export const detectLanguage = async (headers) => { const client = getI18nClient(); return client.detectLanguage(headers); };