feat: Complete localization system with i18n and DeepSeek AI translation

🌐 Interface Localization:
- Added i18next for multi-language interface support
- Created LocalizationService with language detection
- Added translation files for Russian and English
- Implemented language selection in user settings

🤖 AI Profile Translation (Premium feature):
- Integrated DeepSeek API for profile translation
- Added TranslationController for translation management
- Premium-only access to profile translation feature
- Support for 10 languages (ru, en, es, fr, de, it, pt, zh, ja, ko)

�� Database & Models:
- Added language field to users table with migration
- Updated User model to support language preferences
- Added language constraints and indexing

🎛️ User Interface:
- Added language settings menu in bot settings
- Implemented callback handlers for language selection
- Added translate profile button for VIP users
- Localized all interface strings

📚 Documentation:
- Created comprehensive LOCALIZATION.md guide
- Documented API usage and configuration
- Added examples for extending language support
This commit is contained in:
2025-09-13 08:59:10 +09:00
parent 975eb348dd
commit edddd52589
12 changed files with 992 additions and 8 deletions

View File

@@ -0,0 +1,171 @@
import axios from 'axios';
export interface TranslationRequest {
text: string;
targetLanguage: string;
sourceLanguage?: string;
}
export interface TranslationResponse {
translatedText: string;
sourceLanguage: string;
targetLanguage: string;
}
export class DeepSeekTranslationService {
private static instance: DeepSeekTranslationService;
private apiKey: string;
private apiUrl: string = 'https://api.deepseek.com/v1/chat/completions';
private constructor() {
this.apiKey = process.env.DEEPSEEK_API_KEY || '';
if (!this.apiKey) {
console.warn('⚠️ DEEPSEEK_API_KEY not found in environment variables');
}
}
public static getInstance(): DeepSeekTranslationService {
if (!DeepSeekTranslationService.instance) {
DeepSeekTranslationService.instance = new DeepSeekTranslationService();
}
return DeepSeekTranslationService.instance;
}
public async translateProfile(request: TranslationRequest): Promise<TranslationResponse> {
if (!this.apiKey) {
throw new Error('DeepSeek API key is not configured');
}
try {
const prompt = this.createTranslationPrompt(request);
const response = await axios.post(this.apiUrl, {
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: 'You are a professional translator specializing in dating profiles. Translate the given text naturally, preserving the tone and personality. Respond only with the translated text, no additional comments.'
},
{
role: 'user',
content: prompt
}
],
max_tokens: 1000,
temperature: 0.3
}, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
timeout: 30000 // 30 секунд таймаут
});
if (response.data?.choices?.[0]?.message?.content) {
const translatedText = response.data.choices[0].message.content.trim();
return {
translatedText,
sourceLanguage: request.sourceLanguage || 'auto',
targetLanguage: request.targetLanguage
};
} else {
throw new Error('Invalid response from DeepSeek API');
}
} catch (error) {
console.error('❌ DeepSeek translation error:', error);
if (axios.isAxiosError(error)) {
if (error.response?.status === 401) {
throw new Error('Invalid DeepSeek API key');
} else if (error.response?.status === 429) {
throw new Error('Translation rate limit exceeded. Please try again later.');
} else if (error.code === 'ECONNABORTED') {
throw new Error('Translation request timed out. Please try again.');
}
}
throw new Error('Translation service temporarily unavailable');
}
}
private createTranslationPrompt(request: TranslationRequest): string {
const languageMap: { [key: string]: string } = {
'en': 'English',
'ru': 'Russian',
'es': 'Spanish',
'fr': 'French',
'de': 'German',
'it': 'Italian',
'pt': 'Portuguese',
'zh': 'Chinese',
'ja': 'Japanese',
'ko': 'Korean'
};
const targetLanguageName = languageMap[request.targetLanguage] || request.targetLanguage;
let prompt = `Translate the following dating profile text to ${targetLanguageName}. `;
if (request.sourceLanguage && request.sourceLanguage !== 'auto') {
const sourceLanguageName = languageMap[request.sourceLanguage] || request.sourceLanguage;
prompt += `The source language is ${sourceLanguageName}. `;
}
prompt += `Keep the tone natural and personal, as if the person is describing themselves:\n\n${request.text}`;
return prompt;
}
// Определить язык текста (базовая логика)
public detectLanguage(text: string): string {
// Простая эвристика для определения языка
const cyrillicPattern = /[а-яё]/i;
const latinPattern = /[a-z]/i;
const cyrillicCount = (text.match(cyrillicPattern) || []).length;
const latinCount = (text.match(latinPattern) || []).length;
if (cyrillicCount > latinCount) {
return 'ru';
} else if (latinCount > 0) {
return 'en';
}
return 'auto';
}
// Проверить доступность сервиса
public async checkServiceAvailability(): Promise<boolean> {
if (!this.apiKey) {
return false;
}
try {
const response = await axios.post(this.apiUrl, {
model: 'deepseek-chat',
messages: [
{
role: 'user',
content: 'Test'
}
],
max_tokens: 5
}, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
timeout: 10000
});
return response.status === 200;
} catch (error) {
console.error('DeepSeek service availability check failed:', error);
return false;
}
}
}
export default DeepSeekTranslationService;

View File

@@ -0,0 +1,105 @@
import i18next from 'i18next';
import * as fs from 'fs';
import * as path from 'path';
export class LocalizationService {
private static instance: LocalizationService;
private initialized = false;
private constructor() {}
public static getInstance(): LocalizationService {
if (!LocalizationService.instance) {
LocalizationService.instance = new LocalizationService();
}
return LocalizationService.instance;
}
public async initialize(): Promise<void> {
if (this.initialized) return;
try {
// Загружаем файлы переводов
const localesPath = path.join(__dirname, '..', 'locales');
const ruTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'ru.json'), 'utf8'));
const enTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'en.json'), 'utf8'));
await i18next.init({
lng: 'ru', // Язык по умолчанию
fallbackLng: 'ru',
debug: false,
resources: {
ru: {
translation: ruTranslations
},
en: {
translation: enTranslations
}
},
interpolation: {
escapeValue: false
}
});
this.initialized = true;
console.log('✅ Localization service initialized successfully');
} catch (error) {
console.error('❌ Failed to initialize localization service:', error);
throw error;
}
}
public t(key: string, options?: any): string {
return i18next.t(key, options) as string;
}
public setLanguage(language: string): void {
i18next.changeLanguage(language);
}
public getCurrentLanguage(): string {
return i18next.language;
}
public getSupportedLanguages(): string[] {
return ['ru', 'en'];
}
// Получить перевод для определенного языка без изменения текущего
public getTranslation(key: string, language: string, options?: any): string {
const currentLang = i18next.language;
i18next.changeLanguage(language);
const translation = i18next.t(key, options) as string;
i18next.changeLanguage(currentLang);
return translation;
}
// Определить язык пользователя по его настройкам Telegram
public detectUserLanguage(telegramLanguageCode?: string): string {
if (!telegramLanguageCode) return 'ru';
// Поддерживаемые языки
const supportedLanguages = this.getSupportedLanguages();
// Проверяем точное совпадение
if (supportedLanguages.includes(telegramLanguageCode)) {
return telegramLanguageCode;
}
// Проверяем по первым двум символам (например, en-US -> en)
const languagePrefix = telegramLanguageCode.substring(0, 2);
if (supportedLanguages.includes(languagePrefix)) {
return languagePrefix;
}
// По умолчанию русский
return 'ru';
}
}
// Функция-хелпер для быстрого доступа к переводам
export const t = (key: string, options?: any): string => {
return LocalizationService.getInstance().t(key, options);
};
export default LocalizationService;