Metadata-Version: 2.4
Name: auto-i18n-lib
Version: 2.2.5
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/TS/TSX), переводит их через ИИ, проверяет каждый перевод и подменяет
текст в уже отрисованной странице на выбранный язык. Вызовы вида `t('key')`,
атрибуты `data-i18n` и любые другие доработки компонентов не нужны.

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

---

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

- Python >= 3.9.
- Node.js — для разбора JS/JSX/TS/TSX настоящим AST-парсером
  (`@babel/parser` + `@babel/traverse`). Нужен только там, где идёт
  сканирование (воркер, `autoi18n scan`), для отдачи страниц не нужен.
- Зависимости JS-моста ставятся один раз после установки пакета:
  ```
  cd <site-packages>/autoi18n/extractor/js_bridge
  npm install --omit=dev
  ```
- Ключ OpenAI (`OPENAI_API_KEY`) — для перевода. Сканирование в режиме
  `--dry-run` работает без него.

---

## Как это работает

1. **Сканирование.** Воркер обходит папки проекта и собирает фразы
   интерфейса (см. «Что считается текстом интерфейса»).
2. **Реестр.** Ключ хранения — хеш текста на исходном языке. Файл исходного
   языка (`translations/<source_lang>.json`) — реестр всех фраз проекта.
   Новые фразы и любые известные фразы без перевода ставятся в очередь на
   все целевые языки.
3. **Перевод.** Для каждой фразы из очереди:
   1. фраза целиком есть в глоссарии проекта → перевод из глоссария, ИИ не
      вызывается;
   2. иначе → ИИ (OpenAI) с описанием приложения и терминами глоссария,
      затем проверка ответа (см. «Контроль качества»).
4. **Самоконтроль.** На каждом цикле воркер проверяет и уже сохранённые
   переводы: глоссарий применяется к ним с приоритетом, плохие переводы
   переводятся заново.
5. **Документы.** Файлы `.md` из папок документов переводятся целиком,
   перевод кладётся рядом с исходником.
6. **Показ.** HTML, отрисованный на сервере, переводится `apply_to_html()`;
   страницы фронтенда переводит клиентский рантайм прямо в браузере.
7. **Отчёт.** Итоги каждого цикла и все проблемы пишутся в `_report.json`
   и `_autoi18n.log`.

Данные, которые появляются только в момент рендера (содержимое БД, ввод
пользователя), библиотека не видит и не трогает — переводятся только тексты
из файлов проекта.

---

## Подключение к проекту

1. `.env` проекта:
   ```
   SOURCE_LANG=ru
   AUTO_I18N_TARGET_LANGS=en,de
   OPENAI_API_KEY=sk-...
   ```
2. Бэкенд — один экземпляр и фоновый воркер:
   ```python
   import threading
   from autoi18n import Translator

   t = Translator(env_path=".env")
   threading.Thread(target=t.run_translation_loop, kwargs={"interval": 1800}, daemon=True).start()
   ```
3. HTML, отрисованный на сервере:
   ```python
   html = t.apply_to_html(rendered_html, lang=current_lang)
   ```
   JSON-ответы API — `t.apply_to_dict(data, lang)`.
4. Фронтенд — рантайм подключается один раз в точке входа. Бэкенд отдаёт
   его и словарь для переключения языка:
   ```python
   t.build_runtime(lang, dynamic_dom_enabled=True,
                   translations_url_template="/i18n/translations?lang={lang}")
   t.get_translations_dict(lang)   # ответ эндпоинта /i18n/translations
   ```
   Переключение языка на клиенте: `window.autoI18n.setLanguage('en')`.
   Текущий язык — `window.autoI18n.currentLang`, событие смены языка —
   `autoi18nlangchange`, выбор сохраняется в `localStorage`.
5. При необходимости — файл данных проекта `autoi18n.json` (см. ниже).

---

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

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

```json
{
  "context": "CRM для отдела продаж.",
  "scan_paths": [
    {"path": "frontend/src", "type": "js"},
    {"path": "templates", "type": "html"}
  ],
  "attributes": ["data-tip"],
  "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` можно задать явно. |
| `attributes` | Свои атрибуты/свойства, значение которых — видимый текст (например, подсказки `data-tip`). Добавляются к стандартным; их учитывают сканер и рантайм. |
| `doc_paths` | Папки документов `.md`, переводимых целиком. |
| `terms` | Глоссарий (см. ниже). |

**Глоссарий (`terms`).** Ключ — текст на исходном языке, значение —
переводы по языкам. `*` в ключе — любое окончание слова («Менеджер*»
совпадает с «менеджера», «менеджеров»); без `*` — отдельное слово.

- Фраза интерфейса **целиком** совпадает с ключом без `*` (аббревиатура,
  сокращение) → перевод берётся из глоссария, ИИ не вызывается. Такой
  перевод имеет приоритет и над уже сохранённым.
- Термин встречается внутри фразы → передаётся ИИ как обязательный и
  проверяется в готовом переводе (без учёта регистра и окончания).

**Значения по умолчанию для `scan_paths`:**

| Папка | Тип |
|---|---|
| `frontend/src`, `src`, `app` | JS/JSX/TS/TSX |
| `templates`, `app/templates`, `backend/templates` | HTML |

Служебные папки (`node_modules`, `.git`, `dist`, `build`, `.venv` и т.п.)
пропускаются.

---

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

- **JSX** — текст между тегами; значения переводимых атрибутов; строки и
  обе ветки тернарника `{cond ? 'А' : 'Б'}`, запасное значение `a || 'Текст'`
  в JSX; элементы массива, выводимые через `.map()`; аргументы локальных
  функций-рендереров; результаты функций, вызванных прямо в JSX (в том числе
  импортированных из других файлов проекта).
- **HTML** — видимый текст и значения переводимых атрибутов, `value` у
  кнопок; `<script>` внутри HTML разбирается тем же AST-парсером. Вставки
  серверных шаблонов (`{{ … }}`, `{% … %}`, `{# … #}`) в коде скрипта перед
  разбором нейтрализуются, внутри строк остаются как есть.
- **JS** — `el.textContent`, `el.innerText`, `el.innerHTML`, аргументы
  `alert()` / `confirm()`.
- **Переводимые атрибуты** — `title`, `placeholder`, `alt`, `aria-label`,
  `label` и атрибуты из `attributes` проекта.
- Шаблонные строки `` `Вопрос ${n}` `` сохраняются как фразы с параметрами
  (`Вопрос {{0}}`); рантайм сопоставляет их с живым текстом по маске.

Прочие строковые литералы (CSS-значения, id, классы, URL и т.п.) не
собираются.

---

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

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

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

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

Новая фраза, у которой после повторной попытки осталось только нарушение
глоссария, записывается с отметкой в отчёте. Исправление уже сохранённого
плохого перевода принимается только без единого замечания; до этого
остаётся старый перевод.

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

---

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

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

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

---

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

В папке переводов:

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

---

## Файлы в папке переводов

| Файл | Содержимое |
|---|---|
| `<source_lang>.json` | Реестр фраз: `{хеш: текст}`. |
| `<lang>.json` | Переводы: `{хеш: перевод}`. |
| `_pending.json` | Очередь на перевод. |
| `_docs_failed.json` | Документы, перевод которых не удался. |
| `_report.json`, `_autoi18n.log` | Отчёт и журнал. |
| `backend/…` | Словари бэкенд-фраз (`register_keys`, `translate_key`). |

---

## Методы `Translator`

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

---

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

| Переменная | Описание |
|---|---|
| `SOURCE_LANG` | Исходный язык (по умолчанию `ru`). |
| `AUTO_I18N_TARGET_LANGS` | Целевые языки через запятую. |
| `OPENAI_API_KEY` | Ключ OpenAI. |
| `AUTO_I18N_CACHE_DIR` | Папка переводов (по умолчанию `./translations`). |
| `AUTO_I18N_PROJECT_FILE` | Файл данных проекта (по умолчанию `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 docs [--dry-run] [--force] [--path <папка>]
                                   перевести документы .md
autoi18n add-lang <lang>           добавить язык и перевести на него реестр
autoi18n audit [--lang] [--json]   проверить сохранённые переводы
autoi18n retranslate --audit | --text "..." | --contains "..." [--lang] [--no-run]
                                   перевести выбранные фразы заново
autoi18n set <lang> "<исходный текст>" "<перевод>"
                                   ручная правка перевода
autoi18n report [--json]           итоги последнего цикла
autoi18n coverage <lang>           процент покрытия
autoi18n langs                     исходный и целевые языки
```

---

## Лицензия

MIT
