Metadata-Version: 2.4
Name: auto-i18n-lib
Version: 2.2.3
Summary: Post-render HTML and frontend UI dictionary translation, plus whole-document Markdown translation, for Python projects with OpenAI-backed caching and quality checks
Author-email: Andrey Bondarenko <bona.plus2030@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://bona-plus.ru
Project-URL: Source, https://github.com/Aalam2000/autoi18n
Project-URL: Issues, https://github.com/Aalam2000/autoi18n/issues
Keywords: i18n,l10n,translation,html,fastapi,flask,jinja2,openai
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openai>=1.0.0
Requires-Dist: python-dotenv>=1.0.0
Dynamic: license-file

# auto-i18n-lib

**Автоматический перевод интерфейса проекта без единой правки кода самого проекта.**
Плюс перевод документов (`.md`) целиком — файлом рядом с исходником.

Библиотека сама находит тексты в исходниках (HTML-шаблоны и JS/JSX/TSX),
переводит их через ИИ и на лету подменяет их в уже отрисованной странице.
Чтобы подключить её к проекту, код проекта дорабатывать не нужно — ни
`t('key')`, ни `data-i18n`, ни любые другие вызовы в компонентах не
требуются.

---

## Требования

- Python >= 3.9
- **Node.js (обязательно)** — используется для разбора JS/JSX/TSX через
  настоящий AST-парсер (`@babel/parser` + `@babel/traverse`), а не через
  регулярные выражения. Без Node.js извлечение строк из JS/JSX-файлов
  работать не будет. Node нужен только на этапе сканирования проекта
  (`autoi18n scan` / воркер), в рантайме отдачи страниц он не требуется.
- Перед первым использованием — установить зависимости JS-моста:
  ```
  cd <папка_библиотеки>/src/autoi18n/extractor/js_bridge
  npm install
  ```

---

## Архитектура в двух словах

- **Ключ хранения — это хэш текста на исходном языке**, а не
  семантический ID, который придумывает разработчик. Совпадающий текст в
  разных местах проекта — это одна и та же фраза.
- **Файл исходного языка (`translations/<source_lang>.json`) — сам по
  себе реестр всех известных фраз проекта.** Отдельного файла-словаря
  ключей нет.
- **Сканирование (`extract`) сравнивает найденные в файлах фразы только с
  этим реестром.** Новые фразы добавляются в реестр и ставятся в очередь
  на перевод на все активные целевые языки. Кроме того, любая известная
  фраза, у которой нет перевода на целевой язык, снова ставится в очередь.
- **Каждый ответ ИИ проверяется до записи** (см. «Контроль качества»).
- Библиотека сканирует **только файлы проекта** (HTML-шаблоны,
  JS/JSX/TSX). Контент, который заполняется в момент рендера — данные из
  БД, ответы пользователя, введённые им данные и т.п. — она никогда не
  видит и не трогает.
- **Исходный язык и целевые языки — из `.env`**, это единственный
  источник истины (`SOURCE_LANG`, `AUTO_I18N_TARGET_LANGS`). Новый
  целевой язык можно добавить в любой момент — `add_target_lang()`
  (например, из админки проекта) сразу дописывает `.env` (переживает
  рестарт) и переводит на него весь текущий реестр, не дожидаясь
  следующего цикла воркера.
- **Обязательный отладочный этап до подключения ИИ**: `autoi18n scan
  --dry-run` показывает, что именно нашла библиотека — без записи
  чего-либо и без обращения к ИИ (ключ API для этой команды не нужен).
  Проверяете список руками на мусор/пропуски и только после этого
  запускаете реальное сканирование и перевод.
- **Клиентский рантайм ничего не требует от кода компонентов.** После
  отрисовки страницы он обходит уже готовый DOM (`TreeWalker`) и
  подменяет видимый текст на перевод; изменения DOM после первой отрисовки
  (React-перерисовка, обновление счётчика) подхватываются через
  `MutationObserver`. Параметризованные фразы («Вопрос {{0}} / {{1}}»)
  сопоставляются с живым текстом на странице по маске, скомпилированной
  из перевода с плейсхолдерами.

---

## Быстрый старт

```python
from autoi18n import Translator

t = Translator(env_path=".env")
```

`.env` проекта:
```
SOURCE_LANG=ru
AUTO_I18N_TARGET_LANGS=en,az
OPENAI_API_KEY=sk-...
```

### 1. Отладочный этап (обязательно перед подключением ИИ)

```bash
autoi18n scan --dry-run
```

Выводит список найденных фраз (файл, строка, текст, число параметров) —
ничего не пишет на диск. Проверьте на мусор и пропуски.

### 2. Реальное сканирование

```bash
autoi18n scan
```

Новые фразы уходят в реестр исходного языка и в очередь на перевод для
всех целевых языков из `.env`.

### 3. Перевод очереди

```bash
autoi18n translate
```

Требует настроенного ИИ (по умолчанию — OpenAI, `OPENAI_API_KEY`).

### 4. Добавить язык «на лету» (например, из админки)

```bash
autoi18n add-lang en
```
или из кода:
```python
t.add_target_lang("en")
```
Дописывает `.env` и сразу переводит весь реестр на новый язык.

### Фоновый цикл (сканирование + перевод по расписанию)

```python
t.run_translation_loop(interval=300)  # раз в 5 минут: extract() -> process_queue()
```

---

## Какие файлы сканируются

Папки задаёт проект (`scan_paths` в `autoi18n.json`). Если не задал —
используется типовой набор (`config.py`, `DEFAULT_SCAN_PATHS`):

| Папка | Расширения | Тип |
|---|---|---|
| `frontend/src` | `.js .jsx .ts .tsx` | JS/JSX (AST) |
| `src` | `.js .jsx .ts .tsx` | JS/JSX (AST) |
| `app` | `.js .jsx .ts .tsx` | JS/JSX (AST) |
| `templates` | `.html` | HTML |
| `app/templates` | `.html` | HTML |
| `backend/templates` | `.html` | HTML |

Поиск файлов — явный обход папок (`os.walk`) по списку расширений, без
glob-паттернов и без brace-expansion (`*.{js,jsx}`) — именно такие
паттерны в v1 молча находили ноль файлов, так как `glob` в Python их не
поддерживает. Служебные папки (`node_modules`, `.git`, `dist`, `build`,
`.venv` и т.п.) пропускаются автоматически.

### Что именно считается «текстом интерфейса»

- **JSX** — текст между тегами и переводимые атрибуты
  (`label`, `placeholder`, `title`, `aria-label`).
- **HTML** — видимый текст и переводимые атрибуты; содержимое
  `<script>` внутри HTML разбирается тем же JS/AST-парсером.
- **Императивные изменения текста в JS** — только там, где строка
  структурно и есть видимый текст страницы: `el.textContent = ...`,
  `el.innerText = ...`, `el.innerHTML = ...`, а также аргументы
  `alert()`/`confirm()`.

Обычные строковые литералы вне этих мест (CSS-в-JS значения, id,
классы, URL и т.п.) намеренно не трогаются — иначе в реестр попадал бы
технический мусор.

---

## Контроль качества перевода

Перевод фразы записывается, только если прошёл проверку:

- параметры `{{0}}`, `{{1}}` совпадают с исходником;
- нет букв исходного алфавита (например, кириллицы в азербайджанском тексте);
- нет объяснений модели вместо перевода («The translation of … is …»);
- скобки `() [] {}` парные так же, как в исходнике;
- длина разумная;
- соблюдены термины глоссария.

Не прошедшая проверку фраза переводится повторно — поштучно и с указанием,
что было не так. Если жёсткие проверки так и не пройдены, перевод не
записывается: фраза остаётся в очереди со счётчиком попыток и списком
проблем. После `AUTO_I18N_MAX_ATTEMPTS` попыток (по умолчанию 3) она больше
не отправляется в ИИ и видна в `autoi18n audit`.

Уже сохранённые переводы воркер тоже проверяет на каждом цикле. Плохие
ставятся на повторный перевод; старый перевод остаётся на месте, пока новый
не пройдёт проверку полностью (включая глоссарий), с тем же лимитом попыток. Счётчик попыток привязан к
версии библиотеки: после её обновления такие фразы и документы пробуются снова. Нарушение только глоссария
после повторной попытки допускается (перевод записывается и попадает в лог).

Модели передаются системная инструкция (только перевод, без пояснений и
кавычек, не трогать `{{n}}`), описание приложения и термины глоссария;
`temperature=0`, для пакетов — JSON-режим (если модель не поддерживает
параметр, он отключается автоматически).

---

## Данные проекта: файл `autoi18n.json`

Библиотека — механизм, данные — у проекта. Всё, что относится к
конкретному проекту, проект держит у себя в файле `autoi18n.json` в рабочей
папке приложения (рядом с `.env`, в git проекта). Библиотека сама находит
этот файл; код проекта для этого менять не нужно. Без файла библиотека
работает со значениями по умолчанию.

```json
{
  "context": "CRM для отдела продаж.",
  "scan_paths": [
    {"path": "frontend/src", "type": "js"},
    {"path": "templates", "type": "html"}
  ],
  "doc_paths": ["docs"],
  "terms": {
    "КП":           {"en": "Quote",       "de": "Angebot"},
    "Менеджер*":    {"en": "Manager",     "de": "Manager"},
    "баз* клиент*": {"en": "Client base", "de": "Kundenbasis"}
  }
}
```

- `context` — одно-два предложения о приложении для ИИ.
- `scan_paths` — папки, где искать фразы интерфейса: `type` `js`
  (`.js .jsx .ts .tsx`), `html` (`.html`) или `auto`; `extensions` можно
  задать явно. Если не задано — типовой набор (см. «Какие файлы сканируются»).
- `doc_paths` — папки документов `.md` (см. ниже).
- `terms` — глоссарий. Ключ на исходном языке; `*` — любое окончание слова
  («Менеджер*» совпадает с «менеджера», «менеджеров»); без `*` — отдельное
  слово.
  - Фраза интерфейса **целиком** совпадает с ключом без `*` (аббревиатура,
    сокращение: «КП») — перевод берётся из глоссария, ИИ не вызывается.
    Такой перевод имеет приоритет и над уже сохранённым: поправили
    глоссарий — воркер заменит перевод на следующем цикле.
  - Иначе термин передаётся ИИ как обязательный и проверяется в переводе
    (без учёта регистра и окончания).

Файл перечитывается при изменении. Путь можно задать иначе:
`Translator(glossary_path=...)`, `AUTO_I18N_GLOSSARY`, `AUTO_I18N_PROJECT_FILE`.
Без файла глоссарий ищется в `<cache_dir>/_glossary.json`.

---

## Документы (`.md`) — перевод целиком

Документы из папок `doc_paths` переводятся целиком, а не по фразам, и
перевод кладётся файлом рядом с исходником — в общие словари фраз они не
попадают:

```
help/teacher.md      исходник на исходном языке
help/teacher.en.md   перевод (создаёт библиотека)
help/teacher.az.md
```

- Переводит тот же фоновый цикл (`run_translation_loop`) после очереди фраз;
  вручную — `autoi18n docs`.
- Первая строка перевода — служебный комментарий
  `<!-- autoi18n: source=teacher.md lang=en sha1=… -->` с хешем исходника:
  по нему видно, что исходник изменился и перевод нужно обновить. Эта же
  строка отличает перевод от исходника.
- Сохраняется вся разметка Markdown: заголовки, списки, таблицы, жирный и
  курсив, ссылки и картинки (адреса не меняются), HTML-вставки, блоки кода
  (не переводятся). Структура каждого куска перевода сверяется с исходником.
- Длинный документ переводится кусками по абзацам. Если хоть один кусок не
  прошёл проверку, файл перевода не пишется; после лимита попыток документ
  не отправляется в ИИ до изменения исходника (или `autoi18n docs --force`).
- Показ: `Translator.get_document(path, lang)` → `{"text", "lang"}` — перевод
  без служебной строки; если перевода ещё нет, исходник.
- Другие `.md` в проекте не трогаются — только папки из `doc_paths`
  (`Translator(doc_paths=...)`, `AUTO_I18N_DOC_PATHS` или `autoi18n.json`).

---

## Отчёт и журнал

Все важные моменты работы библиотеки пишутся в папку переводов:

- **`_report.json`** — подробный отчёт последнего цикла воркера (или
  последней команды CLI):
  - `extract` — сколько файлов просканировано, новых фраз, восстановлено в
    очередь; файлы, которые не удалось разобрать (для `<script>` внутри
    HTML — имя шаблона и номер скрипта), ошибка JS-моста; папки из списка
    сканирования, которых нет в проекте (это нормально для чужой структуры);
  - `phrases` — сколько переведено по языкам; отклонённые фразы с причиной,
    ответом модели и ответом на повторную попытку, ошибкой API, числом
    попыток; принятые с замечанием (глоссарий); ошибки API и неверный
    формат ответа пакета;
  - `documents` — переведённые, не переведённые (причина, ошибка API или
    не пройденные проверки, ответы модели, кусок текста) и пропущенные
    документы;
  - `audit` — плохие сохранённые переводы, фразы с исчерпанными попытками,
    размер очереди, документы без актуального перевода;
  - `errors` — сбои шагов цикла с трассировкой (сбой одного шага не
    останавливает остальные).
- **`_autoi18n.log`** — журнал: строка на каждое важное событие с датой;
  при размере больше 1 МБ старый журнал переименовывается в `_autoi18n.log.1`.
- Причины неудач сохраняются и рядом с самими записями: в `_pending.json`
  (`problems`, `last_response`, `api_error`) и в `_docs_failed.json`.

Кратко посмотреть итоги: `autoi18n report`.

---

## Методы `Translator`

| Метод | Назначение |
|---|---|
| `extract(dry_run=False)` | Сканирует проект; `dry_run=True` — только отчёт, ничего не пишет. |
| `process_queue(batch_size=50)` | Переводит накопленную очередь. |
| `run_translation_loop(interval, batch_size)` | Фоновый цикл: `extract()` → `process_queue()`. |
| `get_target_langs()` | Текущий список целевых языков из `.env`. |
| `add_target_lang(lang)` | Добавляет язык в `.env` и сразу переводит на него весь реестр. |
| `apply_to_html(html, lang)` | Подставляет перевод в уже отрисованный HTML. |
| `apply_to_dict(source_dict, lang, filter_keys=None)` | Переводит значения вложенного словаря (например, JSON-ответ API). |
| `build_runtime(lang, dynamic_dom_enabled=False)` | Генерирует клиентский JS-рантайм для языка. |
| `register_keys(items, dict_name="bot")` | Регистрирует бэкенд-фразы, которых нет в файлах проекта (например, генерируемые сообщения). |
| `translate_key(default, lang, dict_name="bot")` | Перевод бэкенд-фразы по её тексту (не по ключу — ключей больше нет). |
| `get_translation_coverage(lang)` | Процент готовых переводов для языка. |
| `translate_documents(force=False, dry_run=False)` | Переводит документы `.md` без актуального перевода. |
| `get_document(path, lang)` | Текст документа на языке `lang` (или исходник, если перевода нет). |
| `document_status()` | Состояние переводов документов: ok / missing / stale / failed. |
| `audit(lang=None)` | Проверяет сохранённые переводы: плохие, не переведённые, документы. |
| `retranslate(lang, texts, contains, from_audit, run)` | Переводит выбранные фразы заново. |
| `set_translation(lang, source_text, translation)` | Ручная правка перевода по исходному тексту. |

`translate_key` больше не принимает семантический `key` — только текст на
исходном языке (`default`) и язык. Если перевода ещё нет — ставит фразу в
очередь и возвращает исходный текст.

---

## Интеграция с проектом (без правок кода компонентов)

1. На бэкенде — один хук в месте рендера страницы:
   ```python
   html = t.apply_to_html(rendered_html, lang=current_lang)
   ```
   Для JSON-ответов API — аналогично `apply_to_dict(...)`.
2. На фронтенде — один `<script>` с рантаймом, подключённый один раз в
   точке входа:
   ```html
   <script>
   // t.build_runtime(lang, dynamic_dom_enabled=True)
   </script>
   ```
3. Переключение языка на клиенте:
   ```js
   window.autoI18n.setLanguage('en');
   ```
   Рантайм сам подгрузит словарь нового языка и пройдёт по DOM — код
   компонентов трогать не нужно.

---

## Переменные окружения

| Переменная | Описание |
|---|---|
| `OPENAI_API_KEY` | Ключ ИИ-провайдера — нужен только для `translate`/`add-lang`, не для `scan --dry-run`. |
| `SOURCE_LANG` | Исходный язык (по умолчанию `ru`). |
| `AUTO_I18N_TARGET_LANGS` | Целевые языки через запятую — источник истины, редактируется через `add_target_lang()`. |
| `AUTO_I18N_CACHE_DIR` | Папка для файлов переводов (по умолчанию `./translations`). |
| `AUTO_I18N_PROJECT_FILE` | Файл проекта с контекстом, глоссарием и `doc_paths` (по умолчанию `autoi18n.json`). |
| `AUTO_I18N_GLOSSARY` | Отдельный файл глоссария вместо файла проекта. |
| `AUTO_I18N_DOC_PATHS` | Папки документов через запятую (вместо `doc_paths` из файла проекта). |
| `AUTO_I18N_MAX_ATTEMPTS` | Сколько раз пытаться перевести фразу/документ, не прошедшие проверку (по умолчанию 3). |

---

## CLI

```
autoi18n scan --dry-run [--json]   отладочный этап: показать найденное, ничего не писать
autoi18n scan                      реальное сканирование
autoi18n translate [--batch-size]  перевести очередь
autoi18n add-lang <lang>           добавить целевой язык и перевести на него реестр
autoi18n coverage <lang>           процент покрытия перевода
autoi18n langs                     исходный и текущие целевые языки
autoi18n audit [--lang] [--json]   проверить сохранённые переводы
autoi18n retranslate --audit | --text "..." | --contains "..." [--lang] [--no-run]
                                   перевести заново выбранные фразы
autoi18n set <lang> "<исходный текст>" "<перевод>"
                                   ручная правка перевода
autoi18n docs [--dry-run] [--force] [--path <папка>]
                                   перевести документы .md сейчас
autoi18n report [--json]           итоги последнего цикла (_report.json)
```

---

## Лицензия

MIT
