Metadata-Version: 2.4
Name: auto-i18n-lib
Version: 0.5.0
Summary: Post-render HTML and frontend UI dictionary translation for Python projects with OpenAI-backed caching
Author-email: Andrey Bondarenko <bona.plus2030@gmail.com>
License: 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: License :: OSI Approved :: MIT License
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
Dynamic: license-file

# auto-i18n-lib

Lightweight post-render translation for Python applications.

`auto-i18n-lib` translates already rendered HTML and frontend UI dictionaries without changing templates, business logic, or database structure.

The library uses file-based language caches and background translation processing.

## Features

- Post-render HTML translation
- Frontend UI dictionary translation for JS interfaces
- No synchronous translation calls during page render
- Per-language JSON cache
- Shared cache for HTML and UI strings
- Background translation worker
- Nested `dict` / `list` support for UI dictionaries
- Language normalization
- Fallback language chain
- Legacy page cache compatibility

## Installation

```bash
pip install auto-i18n-lib

## Requirements

* Python 3.9+
* OpenAI API key

## Environment

You can configure the library via constructor arguments or environment variables.

```bash
export OPENAI_API_KEY="your_key"
export SOURCE_LANG="ru"
```

Windows PowerShell:

```powershell
$env:OPENAI_API_KEY="your_key"
$env:SOURCE_LANG="ru"
```

## Core idea

### Render mode

During page render, the library:

* reads translations only from ready cache files
* does not call the translation API
* returns original text if translation is missing

### Background mode

In the background, the library:

* scans registered pages
* finds missing strings
* adds them to pending queue
* translates them in batches
* updates language cache files

This keeps page rendering fast and stable.

## Quick start

```python
from autoi18n import Translator

translator = Translator(
    api_key="YOUR_OPENAI_API_KEY",
    cache_dir="./translations",
    source_lang="ru",
)

html = """
<h1>Главная</h1>
<p>Добро пожаловать в систему</p>
<button>Сохранить</button>
"""

translated_html = translator.translate_html(
    html=html,
    target_lang="en",
    page_name="home",
)

print(translated_html)
```

## UI dictionary translation

Use `translate_dict()` for frontend tables, modals, forms, and other JS-driven UI.

```python
ui = {
    "title": "Грузы",
    "filters": {
        "search": "Поиск",
        "reset": "Сбросить"
    },
    "modal": {
        "save": "Сохранить",
        "cancel": "Отмена"
    }
}

translated_ui = translator.translate_dict(
    page_name="cargo",
    dict_name="table",
    source_dict=ui,
    target_lang="en",
)
```

You can also restrict translation to specific keys:

```python
translated_ui = translator.translate_dict(
    page_name="cargo",
    dict_name="table",
    source_dict=ui,
    target_lang="en",
    filter_keys=["title", "search", "save", "cancel"],
)
```

## Django example

### View

```python
import json
from django.shortcuts import render
from autoi18n import Translator

translator = Translator(
    api_key="YOUR_OPENAI_API_KEY",
    cache_dir="./translations",
    source_lang="ru",
)

def cargo_page(request):
    target_lang = translator.detect_browser_lang(
        request.headers.get("Accept-Language", "")
    )

    html = """
    <h1>Грузы</h1>
    <button>Сохранить</button>
    """

    translated_html = translator.translate_html(
        html=html,
        target_lang=target_lang,
        page_name="cargo_page",
    )

    ui = {
        "title": "Грузы",
        "search": "Поиск",
        "empty": "Нет данных",
        "save": "Сохранить",
        "cancel": "Отмена",
    }

    translated_ui = translator.translate_dict(
        page_name="cargo_page",
        dict_name="table_ui",
        source_dict=ui,
        target_lang=target_lang,
    )

    return render(request, "cargo/page.html", {
        "translated_html": translated_html,
        "ui_json": json.dumps(translated_ui, ensure_ascii=False),
    })
```

### Template

```html
<div>
    {{ translated_html|safe }}
</div>

<script>
    window.CARGO_I18N = {{ ui_json|safe }};
</script>
```

### JavaScript

```javascript
const i18n = window.CARGO_I18N || {};

document.querySelector("#title").textContent = i18n.title || "Грузы";
document.querySelector("#search").placeholder = i18n.search || "Поиск";
document.querySelector("#empty").textContent = i18n.empty || "Нет данных";
```

## Background page registration

Register pages for automatic background scanning.

```python
def render_dashboard():
    return """
    <h1>Панель управления</h1>
    <button>Продолжить</button>
    """

translator.register_page(
    page_name="dashboard",
    html_getter=render_dashboard,
    target_langs=["en", "az", "tr"],
)
```

## Background processing

Process all registered pages once:

```python
report = translator.process_all_translations(batch_size=50)
print(report)
```

Run background loop:

```python
translator.run_translation_loop(interval=300, batch_size=50)
```

Run in a separate thread:

```python
import threading

threading.Thread(
    target=translator.run_translation_loop,
    kwargs={"interval": 300, "batch_size": 50},
    daemon=True,
).start()
```

## Public API

### `Translator(...)`

```python
Translator(
    cache_dir="./translations",
    api_key=None,
    source_lang=None,
    model="gpt-4o-mini",
)
```

### `register_page(page_name, html_getter, target_langs, context=None)`

Registers a page for background scanning.

### `scan_page(page_name, target_lang)`

Scans one registered page and returns missing items.

### `scan_all_pages()`

Scans all registered pages and all configured target languages.

### `process_page_translations(page_name, target_lang, batch_size=50)`

Processes missing translations for one page and one language.

### `process_all_translations(batch_size=50)`

Processes all registered pages.

### `run_translation_loop(interval=300, target_lang=None, page_name="page", stop_event=None, batch_size=50)`

Runs continuous background translation loop.

### `translate_text(text, target_lang, page_name="page", prompt_type="normal")`

Returns translated text from cache if available.
If translation is missing, returns original text and adds it to pending queue.

### `translate_html(html, target_lang, page_name="page")`

Translates rendered HTML using cache.
Missing strings remain unchanged until background processing fills them.

### `translate_dict(page_name, dict_name, source_dict, target_lang=None, filter_keys=None)`

Translates frontend UI dictionaries.

Rules:

* supports nested `dict` and `list`
* stores keys as `ui::page_name::dict_name::path`
* returns original values if translation is missing
* uses the same cache and pending queue as HTML translation

### `enqueue_missing_texts(items, target_lang, page_name="page")`

Adds missing items to pending queue.

### `get_pending_entries(target_lang=None)`

Returns current pending items.

### `process_pending_translations(target_lang=None, page_name="page", batch_size=50)`

Processes pending queue.

### `get_translation_coverage(lang)`

Returns translation statistics for one language.

Example response:

```python
{
    "lang": "en",
    "translated": 120,
    "pending": 8,
    "total": 128,
    "percent": 93.75
}
```

### `detect_browser_lang(accept_language)`

Extracts and normalizes browser language from `Accept-Language`.

### `get_alternative_lang(current_lang, browser_lang)`

Returns helper language for language switch logic.

## Language normalization

The library normalizes language codes before using them in cache files.

Examples:

* `EN` -> `en`
* `en_US` -> `en-us`
* `en-US` -> `en-us`

## Fallback chain

When translation is missing, the library checks fallback languages.

Example:

```text
en-us -> en -> source_lang
```

If nothing is found, the original text is returned.

## Cache structure

Translations are stored as JSON files in the cache directory.

```text
translations/
  en.json
  az.json
  tr.json
  _pending.json
```

Example cache content:

```json
{
  "Сохранить": "Save",
  "ui::cargo::table::title": "Cargo",
  "ui::cargo::table::filters.search": "Search"
}
```

## What is translated

The library translates:

* visible HTML text nodes
* button labels
* selected HTML attributes:

  * `placeholder`
  * `title`
  * `alt`
  * `aria-label`
  * `value` for button-like inputs
* frontend UI dictionaries

## What is skipped

The parser skips:

* `script`
* `style`
* `noscript`

It also skips:

* elements with `translate="no"`
* elements with `data-translate="no"`
* element with `id="langSwitch"`
* pure numbers
* number-like strings
* hashes / UUID-like strings
* many technical values

## PDF usage

Do not translate a generated PDF file.

Correct flow:

```text
render HTML -> translate_html -> generate PDF
```

## Recommended integration pattern

Use the library like this:

1. Render HTML as usual
2. Translate rendered HTML through `translate_html()`
3. Pass JS UI strings through `translate_dict()`
4. Run background worker to complete missing translations
5. Generate PDF only from already translated HTML

## Limitations

* The library works on rendered HTML, not templates
* Translation quality depends on source text quality
* Highly unstable HTML reduces cache reuse efficiency
* JS string extraction is manual: developer passes UI dictionaries explicitly

## License

MIT
