12 KiB
План замены хардкод-текстов на локализационные ключи
Текущее состояние
✅ Реализовано:
- Система локализации с i18next
- Выбор языка при первом запуске
- 10 поддерживаемых языков
- Сохранение языка в БД
⚠️ Требуется:
- Извлечение и замена ~500+ хардкод-текстов в коде
- Дополнение языковых файлов
Стратегия замены
Фаза 1: Критически важные пользовательские тексты (СНАЧАЛА)
Приоритет: HIGH
Файлы с наибольшим количеством пользовательских сообщений:
-
src/handlers/messageHandlers.ts (~150 текстов)
- Создание профиля
- Ввод данных (имя, возраст, город, био)
- Валидация ввода
- Сообщения об ошибках
-
src/handlers/callbackHandlers.ts (~200 текстов)
- Кнопки меню
- Просмотр профилей
- Лайки/дислайки
- Настройки профиля
- VIP функции
-
src/handlers/commandHandlers.ts (~50 текстов)
- Команды бота
- Главное меню
- Справка
Фаза 2: Второстепенные тексты
Приоритет: MEDIUM
-
src/services/notificationService.ts (~30 текстов)
- Уведомления о лайках
- Уведомления о матчах
- Уведомления о сообщениях
-
src/handlers/notificationHandlers.ts (~20 текстов)
- Настройки уведомлений
Фаза 3: Служебные тексты
Приоритет: LOW
-
src/services/profileService.ts (~10 текстов)
- Сообщения об ошибках валидации
-
src/services/matchingService.ts (~5 текстов)
- Логирование и отладка
Процесс замены (пошаговый)
Шаг 1: Анализ файла
# Найти все хардкод-тексты
grep -n "'[А-Яа-яЁё]" src/handlers/messageHandlers.ts
grep -n '"[А-Яа-яЁё]' src/handlers/messageHandlers.ts
Шаг 2: Создание ключей локализации
Для каждого найденного текста:
-
Определить категорию:
profile.*- профильbuttons.*- кнопкиerrors.*- ошибкиmessages.*- сообщенияcommands.*- командыsearch.*- поискmatches.*- матчиsettings.*- настройкиnotifications.*- уведомления
-
Создать понятный ключ:
Плохо: "text1", "msg2" Хорошо: "profile.namePrompt", "errors.invalidAge" -
Добавить в ru.json:
{ "profile": { "namePrompt": "👤 Введите ваше имя:", "agePrompt": "🎂 Сколько вам лет?", "cityPrompt": "🌍 В каком городе вы находитесь?" } }
Шаг 3: Замена в коде
Было:
await bot.sendMessage(chatId, '👤 Введите ваше имя:');
Стало:
const userId = msg.from?.id.toString();
const lang = await profileService.getUserLanguage(userId);
localizationService.setLanguage(lang);
await bot.sendMessage(chatId, localizationService.t('profile.namePrompt'));
Оптимизация (для методов класса):
// В начале метода
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:
{
"profile": {
"namePrompt": "👤 Enter your name:",
"agePrompt": "🎂 How old are you?",
"cityPrompt": "🌍 What city are you in?"
}
}
ko.json:
{
"profile": {
"namePrompt": "👤 이름을 입력하세요:",
"agePrompt": "🎂 나이가 어떻게 되세요?",
"cityPrompt": "🌍 어느 도시에 계세요?"
}
}
И так для всех 10 языков.
Примеры типичных замен
1. Простое сообщение
Было:
await bot.sendMessage(chatId, 'Анкеты закончились! Попробуйте позже.');
Стало:
await bot.sendMessage(chatId, localizationService.t('search.noProfiles'));
2. Сообщение с параметрами
Было:
await bot.sendMessage(chatId, `Привет, ${name}! С возвращением!`);
Стало:
// ru.json
{
"welcome": {
"greeting": "Привет, {{name}}! С возвращением!"
}
}
await bot.sendMessage(
chatId,
localizationService.t('welcome.greeting', { name })
);
3. Кнопки
Было:
const keyboard = {
inline_keyboard: [
[{ text: '❤️ Нравится', callback_data: 'like' }],
[{ text: '👎 Не нравится', callback_data: 'dislike' }]
]
};
Стало:
// ru.json
{
"buttons": {
"like": "❤️ Нравится",
"dislike": "👎 Не нравится"
}
}
const keyboard = {
inline_keyboard: [
[{
text: localizationService.t('buttons.like'),
callback_data: 'like'
}],
[{
text: localizationService.t('buttons.dislike'),
callback_data: 'dislike'
}]
]
};
4. Множественное число (плюрализация)
Было:
const text = count === 1
? `У вас ${count} новый матч`
: `У вас ${count} новых матчей`;
Стало:
// ru.json
{
"matches": {
"newCount_one": "У вас {{count}} новый матч",
"newCount_few": "У вас {{count}} новых матча",
"newCount_many": "У вас {{count}} новых матчей"
}
}
await bot.sendMessage(
chatId,
localizationService.t('matches.newCount', { count })
);
Инструменты для автоматизации
Скрипт поиска хардкод-текстов
#!/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
Скрипт проверки покрытия переводами
// 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
Рекомендации
-
Начинайте с самого используемого функционала:
- Регистрация (messageHandlers.ts - createProfile)
- Просмотр анкет (callbackHandlers.ts - showNextCandidate)
- Главное меню (commandHandlers.ts - handleStart)
-
Группируйте ключи логически:
{ "profile": { "prompts": { "name": "...", "age": "...", "city": "..." }, "validation": { "nameLength": "...", "ageRange": "...", "cityRequired": "..." } } } -
Используйте консистентную нотацию:
- Всегда camelCase для ключей
- Всегда точки для разделения уровней
- Prefix для категории (profile, button, error)
-
Не переводите:
- Технические логи
- Callback_data значения
- Имена переменных и функций
-
Делайте переводы качественными:
- Нанимайте 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, секция создания профиля:
# Создайте ветку для работы
git checkout -b localization-phase1-message-handlers
# Начните замену
code src/handlers/messageHandlers.ts
Удачи! 🚀