Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

## Table of Contents

- [What's new (2026-06-22) — GNU gettext Catalog I/O (.po / .mo)](#whats-new-2026-06-22--gnu-gettext-catalog-io-po--mo)
- [What's new (2026-06-22) — ICU-lite MessageFormat (Plural / Select)](#whats-new-2026-06-22--icu-lite-messageformat-plural--select)
- [What's new (2026-06-22) — Locale-Aware List Formatting](#whats-new-2026-06-22--locale-aware-list-formatting)
- [What's new (2026-06-22) — Bidirectional-Text QA (Trojan-Source Scan)](#whats-new-2026-06-22--bidirectional-text-qa-trojan-source-scan)
Expand Down Expand Up @@ -166,6 +167,12 @@

---

## What's new (2026-06-22) — GNU gettext Catalog I/O (.po / .mo)

Read/compile the de-facto translation format. Full reference: [`docs/source/Eng/doc/new_features/v114_features_doc.rst`](docs/source/Eng/doc/new_features/v114_features_doc.rst).

- **`parse_po` / `read_mo` / `GettextCatalog` / `parse_po_file` / `read_mo_file`** (`AC_gettext_translate`, `AC_gettext_ngettext`): the repo pseudo-localises and renders ICU messages but couldn't read GNU gettext `.po`/`.mo`. This parses `.po` (contexts, plurals, the `Plural-Forms` header via `gettext.c2py`), compiles a standards-compliant `.mo` that Python's own `gettext.GNUTranslations` loads, and exposes `gettext`/`ngettext`/`pgettext`. Pure-stdlib, deterministic.

## What's new (2026-06-22) — ICU-lite MessageFormat (Plural / Select)

Render count-aware localised messages. Full reference: [`docs/source/Eng/doc/new_features/v113_features_doc.rst`](docs/source/Eng/doc/new_features/v113_features_doc.rst).
Expand Down
7 changes: 7 additions & 0 deletions README/README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## 目录

- [本次更新 (2026-06-22) — GNU gettext 目录 I/O(.po / .mo)](#本次更新-2026-06-22--gnu-gettext-目录-iopo--mo)
- [本次更新 (2026-06-22) — ICU-lite MessageFormat(复数 / 选择)](#本次更新-2026-06-22--icu-lite-messageformat复数--选择)
- [本次更新 (2026-06-22) — 区域感知列表格式化](#本次更新-2026-06-22--区域感知列表格式化)
- [本次更新 (2026-06-22) — 双向文字 QA(Trojan-Source 扫描)](#本次更新-2026-06-22--双向文字-qatrojan-source-扫描)
Expand Down Expand Up @@ -169,6 +170,12 @@

平滑噪声值序列。完整参考:[`docs/source/Zh/doc/new_features/v102_features_doc.rst`](../docs/source/Zh/doc/new_features/v102_features_doc.rst)。

## 本次更新 (2026-06-22) — GNU gettext 目录 I/O(.po / .mo)

读取/编译事实标准翻译格式。完整参考:[`docs/source/Zh/doc/new_features/v114_features_doc.rst`](../docs/source/Zh/doc/new_features/v114_features_doc.rst)。

- **`parse_po` / `read_mo` / `GettextCatalog` / `parse_po_file` / `read_mo_file`**(`AC_gettext_translate`、`AC_gettext_ngettext`):本项目能伪在地化并渲染 ICU 消息,却无法读取 GNU gettext `.po`/`.mo`。本功能解析 `.po`(上下文、复数、以 `gettext.c2py` 处理 `Plural-Forms` 标头)、编译可被 Python 内建 `gettext.GNUTranslations` 载入的标准 `.mo`,并提供 `gettext`/`ngettext`/`pgettext`。纯标准库、确定。

## 本次更新 (2026-06-22) — ICU-lite MessageFormat(复数 / 选择)

渲染依数量变化的在地化消息。完整参考:[`docs/source/Zh/doc/new_features/v113_features_doc.rst`](../docs/source/Zh/doc/new_features/v113_features_doc.rst)。
Expand Down
7 changes: 7 additions & 0 deletions README/README_zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

## 目錄

- [本次更新 (2026-06-22) — GNU gettext 目錄 I/O(.po / .mo)](#本次更新-2026-06-22--gnu-gettext-目錄-iopo--mo)
- [本次更新 (2026-06-22) — ICU-lite MessageFormat(複數 / 選擇)](#本次更新-2026-06-22--icu-lite-messageformat複數--選擇)
- [本次更新 (2026-06-22) — 地區感知清單格式化](#本次更新-2026-06-22--地區感知清單格式化)
- [本次更新 (2026-06-22) — 雙向文字 QA(Trojan-Source 掃描)](#本次更新-2026-06-22--雙向文字-qatrojan-source-掃描)
Expand Down Expand Up @@ -169,6 +170,12 @@

平滑雜訊值序列。完整參考:[`docs/source/Zh/doc/new_features/v102_features_doc.rst`](../docs/source/Zh/doc/new_features/v102_features_doc.rst)。

## 本次更新 (2026-06-22) — GNU gettext 目錄 I/O(.po / .mo)

讀取/編譯事實標準翻譯格式。完整參考:[`docs/source/Zh/doc/new_features/v114_features_doc.rst`](../docs/source/Zh/doc/new_features/v114_features_doc.rst)。

- **`parse_po` / `read_mo` / `GettextCatalog` / `parse_po_file` / `read_mo_file`**(`AC_gettext_translate`、`AC_gettext_ngettext`):本專案能偽在地化並渲染 ICU 訊息,卻無法讀取 GNU gettext `.po`/`.mo`。本功能解析 `.po`(上下文、複數、以 `gettext.c2py` 處理 `Plural-Forms` 標頭)、編譯可被 Python 內建 `gettext.GNUTranslations` 載入的標準 `.mo`,並提供 `gettext`/`ngettext`/`pgettext`。純標準函式庫、具決定性。

## 本次更新 (2026-06-22) — ICU-lite MessageFormat(複數 / 選擇)

渲染依數量變化的在地化訊息。完整參考:[`docs/source/Zh/doc/new_features/v113_features_doc.rst`](../docs/source/Zh/doc/new_features/v113_features_doc.rst)。
Expand Down
48 changes: 48 additions & 0 deletions docs/source/Eng/doc/new_features/v114_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
GNU gettext Catalog I/O (.po / .mo)
===================================

The repo has ``i18n_test`` (pseudo-localisation, catalog placeholder checks) and
``message_format`` (ICU rendering) but no reader for the *de-facto* translation
format — GNU gettext ``.po`` / ``.mo``. This parses ``.po`` text (contexts,
plurals, multi-line strings, escapes, the ``Plural-Forms`` header), compiles the
binary ``.mo`` (the same little-endian format Python's own ``gettext`` reads) and
exposes ``gettext`` / ``ngettext`` / ``pgettext`` lookups.

Pure standard library (``re`` / ``struct`` / ``gettext.c2py`` for the plural
expression); imports no ``PySide6``. Parsing and compilation are pure data
in / data out, so they are fully deterministic in CI.

Headless API
------------

.. code-block:: python

from je_auto_control import parse_po, read_mo, GettextCatalog

catalog = parse_po(po_text)
catalog.gettext("Hello") # 'Hola'
catalog.ngettext("file", "files", 3) # 'archivos'
catalog.pgettext("menu", "Open") # 'Abrir'

mo_bytes = catalog.compile_mo("out/messages.mo") # or .to_mo_bytes()
same = read_mo(mo_bytes) # round-trips, incl. plural rules

# build one by hand
cat = GettextCatalog()
cat.add("apple", ["pomme", "pommes"], plural_id="apples")

``gettext`` returns the translation (or the source ``msgid`` when untranslated);
``ngettext`` evaluates the catalog's ``Plural-Forms`` expression (via
``gettext.c2py``) to pick the right form for ``n``; ``pgettext`` adds a
disambiguation context. ``to_mo_bytes`` / ``compile_mo`` emit a standards-
compliant ``.mo`` that Python's own ``gettext.GNUTranslations`` can load, and
``read_mo`` / ``read_mo_file`` parse one back (little- or big-endian).

Executor commands
-----------------

``AC_gettext_translate`` parses an inline ``.po`` string and returns ``{text}``
for a ``msgid`` (optional ``context``); ``AC_gettext_ngettext`` returns the
plural-correct ``{text}`` for a count ``n``. Both are exposed as MCP tools
(``ac_gettext_translate`` / ``ac_gettext_ngettext``) and as Script Builder
commands under **Data**.
1 change: 1 addition & 0 deletions docs/source/Eng/eng_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ Comprehensive guides for all AutoControl features.
doc/new_features/v111_features_doc
doc/new_features/v112_features_doc
doc/new_features/v113_features_doc
doc/new_features/v114_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
41 changes: 41 additions & 0 deletions docs/source/Zh/doc/new_features/v114_features_doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
GNU gettext 目錄 I/O(.po / .mo)
===============================

本專案已有 ``i18n_test``(偽在地化、目錄佔位符檢查)與 ``message_format``(ICU 渲染),但沒有讀取*事實標準*
翻譯格式 GNU gettext ``.po`` / ``.mo`` 的工具。本功能解析 ``.po`` 文字(上下文、複數、多行字串、跳脫、
``Plural-Forms`` 標頭)、編譯二進位 ``.mo``(與 Python 內建 ``gettext`` 讀取的小端格式相同),並提供
``gettext`` / ``ngettext`` / ``pgettext`` 查詢。

純標準函式庫(``re`` / ``struct`` / 以 ``gettext.c2py`` 處理複數運算式);不匯入 ``PySide6``。解析與編譯皆為
純資料進/資料出,因此在 CI 中完全具決定性。

無頭 API
--------

.. code-block:: python

from je_auto_control import parse_po, read_mo, GettextCatalog

catalog = parse_po(po_text)
catalog.gettext("Hello") # 'Hola'
catalog.ngettext("file", "files", 3) # 'archivos'
catalog.pgettext("menu", "Open") # 'Abrir'

mo_bytes = catalog.compile_mo("out/messages.mo") # 或 .to_mo_bytes()
same = read_mo(mo_bytes) # 可往返,含複數規則

# 以程式手動建立
cat = GettextCatalog()
cat.add("apple", ["pomme", "pommes"], plural_id="apples")

``gettext`` 回傳翻譯(未翻譯時回傳原始 ``msgid``);``ngettext`` 評估目錄的 ``Plural-Forms`` 運算式
(透過 ``gettext.c2py``)以為 ``n`` 選擇正確形式;``pgettext`` 加入消歧上下文。``to_mo_bytes`` / ``compile_mo``
產生符合標準、可被 Python 內建 ``gettext.GNUTranslations`` 載入的 ``.mo``,而 ``read_mo`` / ``read_mo_file``
可反向解析(小端或大端)。

執行器命令
----------

``AC_gettext_translate`` 解析內嵌 ``.po`` 字串並回傳某 ``msgid``(可帶 ``context``)的 ``{text}``;
``AC_gettext_ngettext`` 回傳計數 ``n`` 的複數正確 ``{text}``。兩者皆以 MCP 工具(``ac_gettext_translate`` /
``ac_gettext_ngettext``)以及 Script Builder 中 **Data** 分類下的命令提供。
1 change: 1 addition & 0 deletions docs/source/Zh/zh_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ AutoControl 所有功能的完整使用指南。
doc/new_features/v111_features_doc
doc/new_features/v112_features_doc
doc/new_features/v113_features_doc
doc/new_features/v114_features_doc
doc/ocr_backends/ocr_backends_doc
doc/observability/observability_doc
doc/operations_layer/operations_layer_doc
Expand Down
9 changes: 9 additions & 0 deletions je_auto_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@
from je_auto_control.utils.message_format import (
format_message, ordinal_category, plural_category,
)
# GNU gettext catalog I/O (parse .po, compile/read .mo, message lookup)
from je_auto_control.utils.gettext_catalog import (
GettextCatalog, parse_po, parse_po_file, read_mo, read_mo_file,
)
# CI workflow annotations (GitHub Actions)
from je_auto_control.utils.ci_annotations import (
emit_annotations, format_annotation,
Expand Down Expand Up @@ -998,6 +1002,11 @@ def start_autocontrol_gui(*args, **kwargs):
"format_message",
"ordinal_category",
"plural_category",
"GettextCatalog",
"parse_po",
"parse_po_file",
"read_mo",
"read_mo_file",
"emit_annotations", "format_annotation",
"ClipboardHistory", "default_clipboard_history",
"analyze_heal_log", "heal_stats", "scan_secrets",
Expand Down
20 changes: 20 additions & 0 deletions je_auto_control/gui/script_builder/command_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -2150,6 +2150,26 @@ def _add_resilience_specs(specs: List[CommandSpec]) -> None:
),
description="Render ICU plural/select/selectordinal message.",
))
specs.append(CommandSpec(
"AC_gettext_translate", "Data", "Text: gettext Translate (.po)",
fields=(
FieldSpec("po", FieldType.STRING,
placeholder='msgid "Hello"\\nmsgstr "Hola"'),
FieldSpec("msgid", FieldType.STRING, placeholder="Hello"),
FieldSpec("context", FieldType.STRING, optional=True),
),
description="Look up a singular translation in a gettext .po catalog.",
))
specs.append(CommandSpec(
"AC_gettext_ngettext", "Data", "Text: gettext Plural (.po)",
fields=(
FieldSpec("po", FieldType.STRING, placeholder="(.po source)"),
FieldSpec("msgid", FieldType.STRING, placeholder="file"),
FieldSpec("msgid_plural", FieldType.STRING, placeholder="files"),
FieldSpec("n", FieldType.INT, placeholder="3"),
),
description="Pick the plural-correct translation for count n.",
))
specs.append(CommandSpec(
"AC_diff_rows", "Data", "Dataset Diff: Rows by Key",
fields=(
Expand Down
18 changes: 18 additions & 0 deletions je_auto_control/utils/executor/action_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3031,6 +3031,22 @@ def _format_message(pattern: str, args: Any = None,
return {"text": format_message(pattern, args or {}, locale=locale)}


def _gettext_translate(po: str, msgid: str,
context: Any = None) -> Dict[str, Any]:
"""Adapter: parse a .po string and look up a singular translation."""
from je_auto_control.utils.gettext_catalog import parse_po
catalog = parse_po(po)
return {"text": catalog.gettext(msgid, context=context or None)}


def _gettext_ngettext(po: str, msgid: str, msgid_plural: str,
n: Any) -> Dict[str, Any]:
"""Adapter: parse a .po string and look up a plural translation."""
from je_auto_control.utils.gettext_catalog import parse_po
catalog = parse_po(po)
return {"text": catalog.ngettext(msgid, msgid_plural, int(n))}


def _cas_put(name: str, key: str, value: Any,
expected_version: Any = None) -> Dict[str, Any]:
"""Adapter: optimistic put into a named versioned store."""
Expand Down Expand Up @@ -4722,6 +4738,8 @@ def __init__(self):
"AC_bidi_strip": _bidi_strip,
"AC_format_list": _format_list,
"AC_format_message": _format_message,
"AC_gettext_translate": _gettext_translate,
"AC_gettext_ngettext": _gettext_ngettext,
"AC_detect_drift": _detect_drift,
"AC_categorical_drift": _categorical_drift,
"AC_diff_rows": _diff_rows,
Expand Down
8 changes: 8 additions & 0 deletions je_auto_control/utils/gettext_catalog/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""GNU gettext catalog I/O (parse .po, compile/read .mo, message lookup)."""
from je_auto_control.utils.gettext_catalog.gettext_catalog import (
GettextCatalog, parse_po, parse_po_file, read_mo, read_mo_file,
)

__all__ = [
"GettextCatalog", "parse_po", "parse_po_file", "read_mo", "read_mo_file",
]
Loading
Loading