Skip to main content

Auto-translate GLPI ticket followups using local Ollama LLM

Project description

GLPI Followup Translate

Auto-translate GLPI tickets using a local Ollama LLM. Detects Chinese or English content and translates bidirectionally (zh ↔ en). Works with ticket names, descriptions, followups, tasks, solutions, and validations.

📖 English | 简体中文

Features

  • 🔄 Daemon or one-shot — polling loop or single-pass mode
  • 🌐 Language detection — CJK ratio-aware with mixed CN/EN fallback
  • 🔀 Bidirectional — zh-cn → en, en → zh-cn
  • 📖 Glossary — domain-specific terminology for consistent translations
  • 📝 Preserves original — translation appended, never overwritten
  • 🎨 Rich-text aware — HTML formatting preserved; verbose styles stripped for performance
  • 📦 Full timeline — followups, tasks, solutions, validations (approval request & answer)
  • 🚫 Dedup — content-hash state + in-content markers prevent duplicate translations
  • 🔄 Auto-retry — failed translations retried on next pass
  • ✂️ Chunked translation — long texts split into paragraphs to avoid timeout
  • 📋 Log viewer — view recent logs via --logs command
  • ⚙️ Configurable — polling interval, model, language pairs, glossary, min text length
  • 💻 Cross-platform — Windows, Linux, macOS

Translation Targets

Type Field(s) Method
Ticket name, content PATCH ticket
Followup content PATCH followup
Task content PATCH task
Solution content PATCH solution
Validation submission_comment, approval_comment Create followup (read-only)
Document Skipped (no writable content)

Translation Format

Field Format
Title 原始标题 / Translated title
Description (rich text) <p>原始内容</p><br><br><p><strong>[AUTO-TRANSLATED]</strong></p><p>翻译内容</p>
Description (plain text) 原始内容\n\n[AUTO-TRANSLATED]\n翻译内容
Followup Same as description — rich text or plain text depending on content

Example — Title

服务器无法连接数据库 / The server cannot connect to the database

Example — Rich-Text Description

<p><strong>生产环境</strong>服务器无法连接到
<span style="color: rgb(255, 0, 0);">MySQL数据库</span></p>
<br><br>
<p><strong>[AUTO-TRANSLATED]</strong></p>
<p><strong>Production environment</strong> server cannot connect to the
<span style="color: rgb(255, 0, 0);">MySQL database</span>.</p>

Example — Plain-Text Followup

检查了防火墙规则,发现3306端口被意外关闭。

[AUTO-TRANSLATED]
Checked the firewall rules and found that port 3306 was accidentally closed.

Requirements

  • Python 3.9+
  • Ollama installed and running
  • GLPI instance with API v2.3 and OAuth2 enabled

Quick Start

Option A: pip install (recommended)

pip install glpi-followup-translate
ollama pull kaelri/hy-mt2:1.8b

See Configuration below, then:

glpi-followup-translate              # start daemon
glpi-followup-translate --once       # single pass and exit

More commands: CLI Reference.

For 24/7 service: Run 24/7.

Option B: Development / source install

git clone https://github.com/qingdaoamerasia/glpi-followup-translate.git
cd glpi-followup-translate
pip install -e .
ollama pull kaelri/hy-mt2:1.8b

See Configuration below, then:

glpi-followup-translate              # CLI command
python -m glpi_followup_translate    # or via python module
glpi-followup-translate --once       # single pass

Configuration

# Recommended: XDG standard location (works from any directory)
mkdir -p ~/.config/glpi-followup-translate
cp config.yaml.example ~/.config/glpi-followup-translate/config.yaml
nano ~/.config/glpi-followup-translate/config.yaml

Config search order: ./config.yaml~/.config/glpi-followup-translate/config.yaml<project_root>/config.yaml. Use -c /path/to/config.yaml to override.

glpi:
  api_url: "http://your-glpi-server/api.php/v2.3"
  auth_method: "oauth2_password"
  client_id: "your_client_id"
  client_secret: "your_client_secret"
  username: "your_glpi_username"
  password: "your_glpi_password"

ollama:
  api_url: "http://localhost:11434"
  model: "kaelri/hy-mt2:1.8b"
  timeout: 60

polling:
  interval: 60          # seconds between checks

translation:
  prefix: "[AUTO-TRANSLATED]"
  min_text_length: 0    # 0 = translate any length
  source_languages:
    - "zh-cn"
    - "zh"
    - "en"
  target_language:
    zh-cn: "en"
    zh: "en"
    en: "zh-cn"
  glossary:             # domain-specific terms (source_lang: {term: translation})
    zh-cn:
      工单: "ticket"
      数据库: "database"
      服务器: "server"
    en:
      ticket: "工单"
      database: "数据库"
      server: "服务器"

logging:
  level: "INFO"
  # file: ""  # empty = XDG default (recommended)
Option Description Default
glpi.api_url GLPI API endpoint
glpi.auth_method oauth2_password or app_token oauth2_password
glpi.client_id OAuth2 Client ID
glpi.client_secret OAuth2 Client Secret
glpi.username GLPI login username (oauth2_password)
glpi.password GLPI login password (oauth2_password)
glpi.session_dir GLPI session directory for auto-cleanup (see below) "" (disabled)
glpi.session_max_age Max age in minutes before cleanup 2 × polling.interval
ollama.api_url Ollama API URL http://localhost:11434
ollama.model Translation model kaelri/hy-mt2:1.8b
ollama.timeout Request timeout (seconds) 60
polling.interval Polling interval (seconds) 60
translation.prefix Translation separator marker [AUTO-TRANSLATED]
translation.min_text_length Min plain-text length to translate (0 = no limit) 0
translation.source_languages Language codes to detect ["zh-cn", "zh", "en"]
translation.target_language Source→target language mapping zh-cn→en, zh→en, en→zh-cn
translation.glossary Per-direction term mappings for consistent translation {} (empty)
logging.level Log level (DEBUG, INFO, WARNING, ERROR) INFO
logging.file Log file path (empty = XDG default) "" (auto)

Testing

# Unit tests only (no GLPI/Ollama required)
python test_integration.py --unit

# Single-ticket quick test (Round 1 only)
python test_integration.py --single

# Run first N rounds
python test_integration.py --rounds 3

# Run ALL rounds (full test suite)
python test_integration.py --rounds 0

# List available test rounds
python test_integration.py --list-rounds

# Clean up test tickets created by this script
python test_integration.py --cleanup

Unit Tests (--unit)

Tests language detection, CJK ratio calculation, glossary post-processing, output cleanup, and placeholder round-trip — all without external services.

Integration Tests (--rounds)

Creates [Test]-prefixed tickets on a live GLPI instance, runs a translation pass, and verifies output format and glossary enforcement. Each round targets a specific scenario:

Round Name What it tests
1 Rich-text HTML + mixed followups HTML content, zh/en followup alternation
2 Short text + long text Very short strings and multi-paragraph content
3 Low CJK ratio Predominantly English text with a few Chinese words
4 High CJK ratio Chinese-dominant text with English tech terms
5 Glossary verification Dynamic — generated from config.yaml glossary at runtime
6 English → Chinese All-English tickets translated into Chinese

Round 5 reads glossary terms from config.yaml and generates test tickets that embed those terms, verifying correct translation without hardcoding proprietary vocabulary in the test file.

Run 24/7 (Background Service)

One command, auto-detects your OS:

glpi-followup-translate --install-service     # install
glpi-followup-translate --remove-service      # uninstall
Platform Service
Linux systemd
Windows Task Scheduler
macOS launchd

CLI Reference

glpi-followup-translate [options]
Flag Description
(none) Start daemon — polls GLPI every polling.interval seconds
--once Run a single translation pass and exit
--version Print version and exit
--logs View recent log entries and exit
--log-lines N Number of log lines to display (default: 50)
--follow Continuously tail the log file (implies --logs)
-c PATH Path to config.yaml (default: auto-detect)
--install-service Install as background service (systemd/Task Scheduler/launchd)
--remove-service Remove background service

Session Cleanup (GLPI Inode Exhaustion)

GLPI's Symfony framework creates a PHP session file for every API request — even stateless Bearer-token calls. Without cleanup, these files accumulate and exhaust the filesystem's inodes, crashing GLPI.

The daemon includes automatic session cleanup when glpi.session_dir is configured. After each polling cycle, it deletes session files older than session_max_age minutes.

Setup

Step 1: Check permissions

The daemon needs write access to GLPI's session directory (typically /var/lib/glpi/_sessions, owned by www-data:www-data).

# Check directory ownership and permissions
ls -ld /var/lib/glpi/_sessions
# drwxr-xr-x 2 www-data www-data ... /var/lib/glpi/_sessions

# Check what user your daemon runs as
ps aux | grep glpi-followup-translate
Daemon runs as Can delete sessions? Action needed
root ✅ Yes None — root bypasses file permissions
www-data ✅ Yes None — same owner as session files
Other user (e.g. qais) ❌ No See Step 2

Step 2: Grant permissions (if needed)

If your daemon runs as a non-root, non-www-data user, add it to the www-data group:

# Add daemon user to www-data group
sudo usermod -aG www-data $(whoami)

# Grant group write access to session directory
sudo chmod 775 /var/lib/glpi/_sessions

# Restart daemon to pick up new group
sudo systemctl restart glpi-translate.service

Step 3: Configure

Add to your config.yaml:

glpi:
  # ... existing config ...
  session_dir: "/var/lib/glpi/_sessions"
  # session_max_age: 30   # optional, default = 2x polling interval

Step 4: Verify

# Check session count before
ls /var/lib/glpi/_sessions | wc -l

# Run daemon for a few minutes, then check again
ls /var/lib/glpi/_sessions | wc -l
# Should be stable, not growing

How It Works

  1. Each polling cycle, the daemon makes ~119 API requests to GLPI
  2. Each request creates a PHP session file on the GLPI server
  3. After the cycle, the daemon deletes session files older than session_max_age
  4. Net result: session count stays bounded instead of growing indefinitely

Troubleshooting

Symptom Cause Fix
Permission denied in logs Daemon user can't write to session dir See Step 2 above
Sessions still growing session_dir not configured or wrong path Check config.yaml
Cleanup not running Daemon not using latest code pip install --upgrade glpi-followup-translate

Project Structure

glpi-followup-translate/
├── glpi_followup_translate/
│   ├── __init__.py
│   ├── __main__.py         # entry point
│   ├── config.py           # YAML config loader
│   ├── glpi_client.py      # GLPI REST API v2.3 client
│   ├── main.py             # daemon loop, translation logic, log viewer
│   └── ollama_client.py    # Ollama API client
├── config.yaml.example     # config template (safe to commit)
├── pyproject.toml          # pip package configuration
├── requirements.txt
├── test_integration.py     # unified test suite (unit + integration)
├── CHANGELOG.md
├── README.md
├── README.zh-CN.md
└── CLAUDE.md

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

glpi_followup_translate-0.4.0.tar.gz (43.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

glpi_followup_translate-0.4.0-py3-none-any.whl (40.6 kB view details)

Uploaded Python 3

File details

Details for the file glpi_followup_translate-0.4.0.tar.gz.

File metadata

  • Download URL: glpi_followup_translate-0.4.0.tar.gz
  • Upload date:
  • Size: 43.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for glpi_followup_translate-0.4.0.tar.gz
Algorithm Hash digest
SHA256 89a3667cbcd288f6d313ede6c1c8d7a12098ea56a179173539ba1522ebc351fb
MD5 d40ec0705144878f699a672310da797e
BLAKE2b-256 650007229f50001b069587c48094157f930d263273354114a7b1d5e4016d6af5

See more details on using hashes here.

Provenance

The following attestation bundles were made for glpi_followup_translate-0.4.0.tar.gz:

Publisher: publish.yml on qingdaoamerasia/glpi-followup-translate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file glpi_followup_translate-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for glpi_followup_translate-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9eb7b390b8aa9c1f3dfd7d3b684e48f345d3299579b539f976ba4ebdab5da565
MD5 5fa3019fa697cdf9b97fa8d37afa0551
BLAKE2b-256 ca722fe4fe53271197700fc8d5aae9dcb543648395ef9db6c1fe28b4c6c4d8be

See more details on using hashes here.

Provenance

The following attestation bundles were made for glpi_followup_translate-0.4.0-py3-none-any.whl:

Publisher: publish.yml on qingdaoamerasia/glpi-followup-translate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page