Files
tg_tinder_bot/docs/LOCALIZATION_MIGRATION_PLAN.md
2025-11-24 17:00:20 +09:00

12 KiB
Raw Permalink Blame History

План замены хардкод-текстов на локализационные ключи

Текущее состояние

Реализовано:

  • Система локализации с 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

  1. src/services/notificationService.ts (~30 текстов)

    • Уведомления о лайках
    • Уведомления о матчах
    • Уведомления о сообщениях
  2. src/handlers/notificationHandlers.ts (~20 текстов)

    • Настройки уведомлений

Фаза 3: Служебные тексты

Приоритет: LOW

  1. src/services/profileService.ts (~10 текстов)

    • Сообщения об ошибках валидации
  2. src/services/matchingService.ts (~5 текстов)

    • Логирование и отладка

Процесс замены (пошаговый)

Шаг 1: Анализ файла

# Найти все хардкод-тексты
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:

    {
      "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

Рекомендации

  1. Начинайте с самого используемого функционала:

    • Регистрация (messageHandlers.ts - createProfile)
    • Просмотр анкет (callbackHandlers.ts - showNextCandidate)
    • Главное меню (commandHandlers.ts - handleStart)
  2. Группируйте ключи логически:

    {
      "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, секция создания профиля:

# Создайте ветку для работы
git checkout -b localization-phase1-message-handlers

# Начните замену
code src/handlers/messageHandlers.ts

Удачи! 🚀