Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
Full-stack web application for Telegram management - Frontend: Vue 3 + Vben Admin - Backend: NestJS - Features: User management, group broadcast, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
302
marketing-agent/shared/utils/i18nClient.js
Normal file
302
marketing-agent/shared/utils/i18nClient.js
Normal file
@@ -0,0 +1,302 @@
|
||||
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);
|
||||
};
|
||||
Reference in New Issue
Block a user