431 lines
12 KiB
Markdown
431 lines
12 KiB
Markdown
# План замены хардкод-текстов на локализационные ключи
|
||
|
||
## Текущее состояние
|
||
|
||
✅ **Реализовано:**
|
||
- Система локализации с i18next
|
||
- Выбор языка при первом запуске
|
||
- 10 поддерживаемых языков
|
||
- Сохранение языка в БД
|
||
|
||
⚠️ **Требуется:**
|
||
- Извлечение и замена ~500+ хардкод-текстов в коде
|
||
- Дополнение языковых файлов
|
||
|
||
## Стратегия замены
|
||
|
||
### Фаза 1: Критически важные пользовательские тексты (СНАЧАЛА)
|
||
|
||
#### Приоритет: HIGH
|
||
Файлы с наибольшим количеством пользовательских сообщений:
|
||
|
||
1. **src/handlers/messageHandlers.ts** (~150 текстов)
|
||
- Создание профиля
|
||
- Ввод данных (имя, возраст, город, био)
|
||
- Валидация ввода
|
||
- Сообщения об ошибках
|
||
|
||
2. **src/handlers/callbackHandlers.ts** (~200 текстов)
|
||
- Кнопки меню
|
||
- Просмотр профилей
|
||
- Лайки/дислайки
|
||
- Настройки профиля
|
||
- VIP функции
|
||
|
||
3. **src/handlers/commandHandlers.ts** (~50 текстов)
|
||
- Команды бота
|
||
- Главное меню
|
||
- Справка
|
||
|
||
### Фаза 2: Второстепенные тексты
|
||
|
||
#### Приоритет: MEDIUM
|
||
|
||
4. **src/services/notificationService.ts** (~30 текстов)
|
||
- Уведомления о лайках
|
||
- Уведомления о матчах
|
||
- Уведомления о сообщениях
|
||
|
||
5. **src/handlers/notificationHandlers.ts** (~20 текстов)
|
||
- Настройки уведомлений
|
||
|
||
### Фаза 3: Служебные тексты
|
||
|
||
#### Приоритет: LOW
|
||
|
||
6. **src/services/profileService.ts** (~10 текстов)
|
||
- Сообщения об ошибках валидации
|
||
|
||
7. **src/services/matchingService.ts** (~5 текстов)
|
||
- Логирование и отладка
|
||
|
||
## Процесс замены (пошаговый)
|
||
|
||
### Шаг 1: Анализ файла
|
||
|
||
```bash
|
||
# Найти все хардкод-тексты
|
||
grep -n "'[А-Яа-яЁё]" src/handlers/messageHandlers.ts
|
||
grep -n '"[А-Яа-яЁё]' src/handlers/messageHandlers.ts
|
||
```
|
||
|
||
### Шаг 2: Создание ключей локализации
|
||
|
||
Для каждого найденного текста:
|
||
|
||
1. **Определить категорию:**
|
||
- `profile.*` - профиль
|
||
- `buttons.*` - кнопки
|
||
- `errors.*` - ошибки
|
||
- `messages.*` - сообщения
|
||
- `commands.*` - команды
|
||
- `search.*` - поиск
|
||
- `matches.*` - матчи
|
||
- `settings.*` - настройки
|
||
- `notifications.*` - уведомления
|
||
|
||
2. **Создать понятный ключ:**
|
||
```
|
||
Плохо: "text1", "msg2"
|
||
Хорошо: "profile.namePrompt", "errors.invalidAge"
|
||
```
|
||
|
||
3. **Добавить в ru.json:**
|
||
```json
|
||
{
|
||
"profile": {
|
||
"namePrompt": "👤 Введите ваше имя:",
|
||
"agePrompt": "🎂 Сколько вам лет?",
|
||
"cityPrompt": "🌍 В каком городе вы находитесь?"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Шаг 3: Замена в коде
|
||
|
||
#### Было:
|
||
```typescript
|
||
await bot.sendMessage(chatId, '👤 Введите ваше имя:');
|
||
```
|
||
|
||
#### Стало:
|
||
```typescript
|
||
const userId = msg.from?.id.toString();
|
||
const lang = await profileService.getUserLanguage(userId);
|
||
localizationService.setLanguage(lang);
|
||
|
||
await bot.sendMessage(chatId, localizationService.t('profile.namePrompt'));
|
||
```
|
||
|
||
#### Оптимизация (для методов класса):
|
||
|
||
```typescript
|
||
// В начале метода
|
||
private async sendLocalizedMessage(
|
||
chatId: number,
|
||
userId: string,
|
||
key: string,
|
||
options?: any
|
||
): Promise<void> {
|
||
const lang = await this.profileService.getUserLanguage(userId);
|
||
this.localizationService.setLanguage(lang);
|
||
const text = this.localizationService.t(key, options);
|
||
await this.bot.sendMessage(chatId, text);
|
||
}
|
||
|
||
// Использование
|
||
await this.sendLocalizedMessage(chatId, userId, 'profile.namePrompt');
|
||
```
|
||
|
||
### Шаг 4: Перевод на другие языки
|
||
|
||
После добавления ключа в `ru.json`, добавить переводы:
|
||
|
||
**en.json:**
|
||
```json
|
||
{
|
||
"profile": {
|
||
"namePrompt": "👤 Enter your name:",
|
||
"agePrompt": "🎂 How old are you?",
|
||
"cityPrompt": "🌍 What city are you in?"
|
||
}
|
||
}
|
||
```
|
||
|
||
**ko.json:**
|
||
```json
|
||
{
|
||
"profile": {
|
||
"namePrompt": "👤 이름을 입력하세요:",
|
||
"agePrompt": "🎂 나이가 어떻게 되세요?",
|
||
"cityPrompt": "🌍 어느 도시에 계세요?"
|
||
}
|
||
}
|
||
```
|
||
|
||
И так для всех 10 языков.
|
||
|
||
## Примеры типичных замен
|
||
|
||
### 1. Простое сообщение
|
||
|
||
**Было:**
|
||
```typescript
|
||
await bot.sendMessage(chatId, 'Анкеты закончились! Попробуйте позже.');
|
||
```
|
||
|
||
**Стало:**
|
||
```typescript
|
||
await bot.sendMessage(chatId, localizationService.t('search.noProfiles'));
|
||
```
|
||
|
||
### 2. Сообщение с параметрами
|
||
|
||
**Было:**
|
||
```typescript
|
||
await bot.sendMessage(chatId, `Привет, ${name}! С возвращением!`);
|
||
```
|
||
|
||
**Стало:**
|
||
```json
|
||
// ru.json
|
||
{
|
||
"welcome": {
|
||
"greeting": "Привет, {{name}}! С возвращением!"
|
||
}
|
||
}
|
||
```
|
||
|
||
```typescript
|
||
await bot.sendMessage(
|
||
chatId,
|
||
localizationService.t('welcome.greeting', { name })
|
||
);
|
||
```
|
||
|
||
### 3. Кнопки
|
||
|
||
**Было:**
|
||
```typescript
|
||
const keyboard = {
|
||
inline_keyboard: [
|
||
[{ text: '❤️ Нравится', callback_data: 'like' }],
|
||
[{ text: '👎 Не нравится', callback_data: 'dislike' }]
|
||
]
|
||
};
|
||
```
|
||
|
||
**Стало:**
|
||
```json
|
||
// ru.json
|
||
{
|
||
"buttons": {
|
||
"like": "❤️ Нравится",
|
||
"dislike": "👎 Не нравится"
|
||
}
|
||
}
|
||
```
|
||
|
||
```typescript
|
||
const keyboard = {
|
||
inline_keyboard: [
|
||
[{
|
||
text: localizationService.t('buttons.like'),
|
||
callback_data: 'like'
|
||
}],
|
||
[{
|
||
text: localizationService.t('buttons.dislike'),
|
||
callback_data: 'dislike'
|
||
}]
|
||
]
|
||
};
|
||
```
|
||
|
||
### 4. Множественное число (плюрализация)
|
||
|
||
**Было:**
|
||
```typescript
|
||
const text = count === 1
|
||
? `У вас ${count} новый матч`
|
||
: `У вас ${count} новых матчей`;
|
||
```
|
||
|
||
**Стало:**
|
||
```json
|
||
// ru.json
|
||
{
|
||
"matches": {
|
||
"newCount_one": "У вас {{count}} новый матч",
|
||
"newCount_few": "У вас {{count}} новых матча",
|
||
"newCount_many": "У вас {{count}} новых матчей"
|
||
}
|
||
}
|
||
```
|
||
|
||
```typescript
|
||
await bot.sendMessage(
|
||
chatId,
|
||
localizationService.t('matches.newCount', { count })
|
||
);
|
||
```
|
||
|
||
## Инструменты для автоматизации
|
||
|
||
### Скрипт поиска хардкод-текстов
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# find_hardcoded_texts.sh
|
||
|
||
echo "Поиск русских текстов в кавычках..."
|
||
grep -rn "'[А-Яа-яЁё]" src/ --include="*.ts" | wc -l
|
||
grep -rn '"[А-Яа-яЁё]' src/ --include="*.ts" | wc -l
|
||
|
||
echo "Топ-10 файлов с наибольшим количеством хардкода:"
|
||
grep -rn "'[А-Яа-яЁё]\|\"[А-Яа-яЁё]" src/ --include="*.ts" | \
|
||
cut -d: -f1 | \
|
||
sort | \
|
||
uniq -c | \
|
||
sort -rn | \
|
||
head -10
|
||
```
|
||
|
||
### Скрипт проверки покрытия переводами
|
||
|
||
```typescript
|
||
// scripts/check-translations.ts
|
||
import * as fs from 'fs';
|
||
import * as path from 'path';
|
||
|
||
const localesPath = path.join(__dirname, '..', 'src', 'locales');
|
||
const ruFile = JSON.parse(fs.readFileSync(path.join(localesPath, 'ru.json'), 'utf8'));
|
||
|
||
function getAllKeys(obj: any, prefix = ''): string[] {
|
||
let keys: string[] = [];
|
||
for (const key in obj) {
|
||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
||
keys = keys.concat(getAllKeys(obj[key], fullKey));
|
||
} else {
|
||
keys.push(fullKey);
|
||
}
|
||
}
|
||
return keys;
|
||
}
|
||
|
||
const ruKeys = getAllKeys(ruFile);
|
||
const languages = ['en', 'es', 'fr', 'de', 'it', 'pt', 'zh', 'ja', 'ko'];
|
||
|
||
languages.forEach(lang => {
|
||
const langFile = JSON.parse(fs.readFileSync(path.join(localesPath, `${lang}.json`), 'utf8'));
|
||
const langKeys = getAllKeys(langFile);
|
||
|
||
const missing = ruKeys.filter(key => !langKeys.includes(key));
|
||
|
||
console.log(`\n${lang}.json:`);
|
||
console.log(` Всего ключей: ${langKeys.length}/${ruKeys.length}`);
|
||
if (missing.length > 0) {
|
||
console.log(` Отсутствуют: ${missing.length}`);
|
||
console.log(` Пример: ${missing.slice(0, 5).join(', ')}`);
|
||
} else {
|
||
console.log(` ✅ Все ключи присутствуют`);
|
||
}
|
||
});
|
||
```
|
||
|
||
## Контрольный список (Checklist)
|
||
|
||
### Перед началом замены файла:
|
||
|
||
- [ ] Сделать backup файла или создать новую ветку в Git
|
||
- [ ] Прочитать весь файл, понять структуру
|
||
- [ ] Составить список всех текстов для замены
|
||
|
||
### В процессе замены:
|
||
|
||
- [ ] Заменять по 10-20 текстов за раз
|
||
- [ ] Тестировать после каждой замены
|
||
- [ ] Проверять TypeScript ошибки: `npm run build`
|
||
- [ ] Коммитить изменения: `git commit -m "localize: messageHandlers profile section"`
|
||
|
||
### После замены файла:
|
||
|
||
- [ ] Убедиться, что нет TypeScript ошибок
|
||
- [ ] Протестировать все функции файла в боте
|
||
- [ ] Обновить переводы для всех 10 языков
|
||
- [ ] Запустить скрипт проверки покрытия
|
||
- [ ] Создать Pull Request для review
|
||
|
||
## Рекомендации
|
||
|
||
1. **Начинайте с самого используемого функционала:**
|
||
- Регистрация (messageHandlers.ts - createProfile)
|
||
- Просмотр анкет (callbackHandlers.ts - showNextCandidate)
|
||
- Главное меню (commandHandlers.ts - handleStart)
|
||
|
||
2. **Группируйте ключи логически:**
|
||
```json
|
||
{
|
||
"profile": {
|
||
"prompts": {
|
||
"name": "...",
|
||
"age": "...",
|
||
"city": "..."
|
||
},
|
||
"validation": {
|
||
"nameLength": "...",
|
||
"ageRange": "...",
|
||
"cityRequired": "..."
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
3. **Используйте консистентную нотацию:**
|
||
- Всегда camelCase для ключей
|
||
- Всегда точки для разделения уровней
|
||
- Prefix для категории (profile, button, error)
|
||
|
||
4. **Не переводите:**
|
||
- Технические логи
|
||
- Callback_data значения
|
||
- Имена переменных и функций
|
||
|
||
5. **Делайте переводы качественными:**
|
||
- Нанимайте native speakers для перевода
|
||
- Используйте контекст культуры (эмодзи, формальность)
|
||
- Учитывайте длину текста (для кнопок)
|
||
|
||
## Оценка объема работ
|
||
|
||
### Время на замену (приблизительно):
|
||
|
||
- **messageHandlers.ts**: 4-6 часов
|
||
- **callbackHandlers.ts**: 6-8 часов
|
||
- **commandHandlers.ts**: 2-3 часа
|
||
- **notificationService.ts**: 1-2 часа
|
||
- **Прочие файлы**: 2-3 часа
|
||
|
||
**Итого на замену:** ~15-22 часа
|
||
|
||
### Время на переводы:
|
||
|
||
- **1 язык (native speaker)**: 2-3 часа
|
||
- **9 языков**: 18-27 часов
|
||
|
||
**ОБЩИЙ ОБЪЕМ:** ~33-49 часов
|
||
|
||
## Следующий шаг
|
||
|
||
Начните с файла **src/handlers/messageHandlers.ts**, секция создания профиля:
|
||
|
||
```bash
|
||
# Создайте ветку для работы
|
||
git checkout -b localization-phase1-message-handlers
|
||
|
||
# Начните замену
|
||
code src/handlers/messageHandlers.ts
|
||
```
|
||
|
||
Удачи! 🚀
|