Skip to content

Commit cf64c2f

Browse files
committed
Add ICU-lite MessageFormat (plural/select/selectordinal)
1 parent b09bccd commit cf64c2f

15 files changed

Lines changed: 494 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
## Table of Contents
1515

16+
- [What's new (2026-06-22) — ICU-lite MessageFormat (Plural / Select)](#whats-new-2026-06-22--icu-lite-messageformat-plural--select)
1617
- [What's new (2026-06-22) — Locale-Aware List Formatting](#whats-new-2026-06-22--locale-aware-list-formatting)
1718
- [What's new (2026-06-22) — Bidirectional-Text QA (Trojan-Source Scan)](#whats-new-2026-06-22--bidirectional-text-qa-trojan-source-scan)
1819
- [What's new (2026-06-22) — Readability Scoring](#whats-new-2026-06-22--readability-scoring)
@@ -165,6 +166,12 @@
165166

166167
---
167168

169+
## What's new (2026-06-22) — ICU-lite MessageFormat (Plural / Select)
170+
171+
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).
172+
173+
- **`format_message` / `plural_category` / `ordinal_category`** (`AC_format_message`): `i18n_test.check_catalog` only compares placeholder sets and `interpolate` is flat `${var}` — neither renders `"{count, plural, one {# item} other {# items}}"`. This implements the ICU MessageFormat subset most apps use: `select`, `plural`, `selectordinal` with CLDR categories, exact `=N` selectors, the `#` count, `offset:`, nesting and apostrophe quoting. Injectable plural rules. Pure-stdlib, deterministic.
174+
168175
## What's new (2026-06-22) — Locale-Aware List Formatting
169176

170177
Join items the way a language expects ("A, B, and C"). Full reference: [`docs/source/Eng/doc/new_features/v112_features_doc.rst`](docs/source/Eng/doc/new_features/v112_features_doc.rst).

‎README/README_zh-CN.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目录
1414

15+
- [本次更新 (2026-06-22) — ICU-lite MessageFormat(复数 / 选择)](#本次更新-2026-06-22--icu-lite-messageformat复数--选择)
1516
- [本次更新 (2026-06-22) — 区域感知列表格式化](#本次更新-2026-06-22--区域感知列表格式化)
1617
- [本次更新 (2026-06-22) — 双向文字 QA(Trojan-Source 扫描)](#本次更新-2026-06-22--双向文字-qatrojan-source-扫描)
1718
- [本次更新 (2026-06-22) — 可读性评分](#本次更新-2026-06-22--可读性评分)
@@ -168,6 +169,12 @@
168169

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

172+
## 本次更新 (2026-06-22) — ICU-lite MessageFormat(复数 / 选择)
173+
174+
渲染依数量变化的在地化消息。完整参考:[`docs/source/Zh/doc/new_features/v113_features_doc.rst`](../docs/source/Zh/doc/new_features/v113_features_doc.rst)。
175+
176+
- **`format_message` / `plural_category` / `ordinal_category`**(`AC_format_message`):`i18n_test.check_catalog` 只比较占位符集合、`interpolate` 只做扁平 `${var}`——两者都无法渲染 `"{count, plural, one {# item} other {# items}}"`。本功能实作多数应用会用到的 ICU MessageFormat 子集:`select`、`plural`、`selectordinal` 搭配 CLDR 类别、优先于类别的精确 `=N` 选择器、`#` 数量、`offset:`、嵌套与单引号转义。复数规则可注入。纯标准库、确定。
177+
171178
## 本次更新 (2026-06-22) — 区域感知列表格式化
172179

173180
依某语言的期望串接项目(「A、B and C」)。完整参考:[`docs/source/Zh/doc/new_features/v112_features_doc.rst`](../docs/source/Zh/doc/new_features/v112_features_doc.rst)。

‎README/README_zh-TW.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
## 目錄
1414

15+
- [本次更新 (2026-06-22) — ICU-lite MessageFormat(複數 / 選擇)](#本次更新-2026-06-22--icu-lite-messageformat複數--選擇)
1516
- [本次更新 (2026-06-22) — 地區感知清單格式化](#本次更新-2026-06-22--地區感知清單格式化)
1617
- [本次更新 (2026-06-22) — 雙向文字 QA(Trojan-Source 掃描)](#本次更新-2026-06-22--雙向文字-qatrojan-source-掃描)
1718
- [本次更新 (2026-06-22) — 可讀性評分](#本次更新-2026-06-22--可讀性評分)
@@ -168,6 +169,12 @@
168169

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

172+
## 本次更新 (2026-06-22) — ICU-lite MessageFormat(複數 / 選擇)
173+
174+
渲染依數量變化的在地化訊息。完整參考:[`docs/source/Zh/doc/new_features/v113_features_doc.rst`](../docs/source/Zh/doc/new_features/v113_features_doc.rst)。
175+
176+
- **`format_message` / `plural_category` / `ordinal_category`**(`AC_format_message`):`i18n_test.check_catalog` 只比較佔位符集合、`interpolate` 只做扁平 `${var}`——兩者都無法渲染 `"{count, plural, one {# item} other {# items}}"`。本功能實作多數應用會用到的 ICU MessageFormat 子集:`select`、`plural`、`selectordinal` 搭配 CLDR 類別、優先於類別的精確 `=N` 選擇器、`#` 數量、`offset:`、巢狀與單引號跳脫。複數規則可注入。純標準函式庫、具決定性。
177+
171178
## 本次更新 (2026-06-22) — 地區感知清單格式化
172179

173180
依某語言的期望串接項目(「A、B and C」)。完整參考:[`docs/source/Zh/doc/new_features/v112_features_doc.rst`](../docs/source/Zh/doc/new_features/v112_features_doc.rst)。
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
ICU-lite MessageFormat (Plural / Select)
2+
========================================
3+
4+
``i18n_test.check_catalog`` only compares placeholder *sets* and ``interpolate``
5+
does flat ``${var}`` substitution — neither can render the count-aware messages
6+
real localisation needs, e.g. ``"{count, plural, one {# item} other {# items}}"``.
7+
This implements the ICU MessageFormat subset most apps use.
8+
9+
Pure standard library; imports no ``PySide6``. The plural/ordinal category
10+
functions are pure and the rule callables are injectable, so rendering is fully
11+
deterministic in CI.
12+
13+
Headless API
14+
------------
15+
16+
.. code-block:: python
17+
18+
from je_auto_control import format_message, plural_category, ordinal_category
19+
20+
plural = "{count, plural, one {# item} other {# items}}"
21+
format_message(plural, {"count": 1}) # '1 item'
22+
format_message(plural, {"count": 5}) # '5 items'
23+
24+
select = "{g, select, male {He} female {She} other {They}} won"
25+
format_message(select, {"g": "female"}) # 'She won'
26+
27+
ordinal = "{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}"
28+
format_message(ordinal, {"place": 3}) # '3rd'
29+
30+
plural_category(2) # 'other'
31+
ordinal_category(3) # 'few'
32+
33+
Supported: simple ``{name}`` arguments, ``select`` (e.g. gender), ``plural`` and
34+
``selectordinal`` with the CLDR categories (``zero``/``one``/``two``/``few``/
35+
``many``/``other``), exact ``=N`` selectors that win over a category, the ``#``
36+
count placeholder, a plural ``offset:`` (``#`` becomes count − offset), nested
37+
arguments, and ICU apostrophe quoting (``''`` → ``'``; ``'{'`` → literal brace).
38+
``plural_rules`` / ``ordinal_rules`` let you inject custom category functions;
39+
``locale`` selects the built-ins (``en``, ``fr``).
40+
41+
Executor commands
42+
-----------------
43+
44+
``AC_format_message`` takes a ``pattern`` plus a JSON ``args`` object and returns
45+
``{text}``, accepting ``locale``. It is exposed as the MCP tool
46+
``ac_format_message`` and as a Script Builder command under **Data**.

‎docs/source/Eng/eng_index.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ Comprehensive guides for all AutoControl features.
135135
doc/new_features/v110_features_doc
136136
doc/new_features/v111_features_doc
137137
doc/new_features/v112_features_doc
138+
doc/new_features/v113_features_doc
138139
doc/ocr_backends/ocr_backends_doc
139140
doc/observability/observability_doc
140141
doc/operations_layer/operations_layer_doc
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
ICU-lite MessageFormat(複數 / 選擇)
2+
===================================
3+
4+
``i18n_test.check_catalog`` 只比較佔位符*集合*、``interpolate`` 只做扁平 ``${var}`` 取代——兩者都無法渲染
5+
真正在地化所需的依數量變化訊息,例如 ``"{count, plural, one {# item} other {# items}}"``。本功能實作多數應用
6+
會用到的 ICU MessageFormat 子集。
7+
8+
純標準函式庫;不匯入 ``PySide6``。複數/序數類別函式為純函式,且規則 callable 可注入,因此渲染在 CI 中
9+
完全具決定性。
10+
11+
無頭 API
12+
--------
13+
14+
.. code-block:: python
15+
16+
from je_auto_control import format_message, plural_category, ordinal_category
17+
18+
plural = "{count, plural, one {# item} other {# items}}"
19+
format_message(plural, {"count": 1}) # '1 item'
20+
format_message(plural, {"count": 5}) # '5 items'
21+
22+
select = "{g, select, male {He} female {She} other {They}} won"
23+
format_message(select, {"g": "female"}) # 'She won'
24+
25+
ordinal = "{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}"
26+
format_message(ordinal, {"place": 3}) # '3rd'
27+
28+
plural_category(2) # 'other'
29+
ordinal_category(3) # 'few'
30+
31+
支援:簡單 ``{name}`` 參數、``select``(如性別)、``plural`` 與 ``selectordinal`` 搭配 CLDR 類別
32+
(``zero``/``one``/``two``/``few``/``many``/``other``)、優先於類別的精確 ``=N`` 選擇器、``#`` 數量佔位符、
33+
複數 ``offset:``(``#`` 變為 count − offset)、巢狀參數,以及 ICU 單引號跳脫(``''`` → ``'``;``'{'`` → 字面
34+
大括號)。``plural_rules`` / ``ordinal_rules`` 可注入自訂類別函式;``locale`` 選擇內建規則(``en``、``fr``)。
35+
36+
執行器命令
37+
----------
38+
39+
``AC_format_message`` 接受 ``pattern`` 與 JSON ``args`` 物件並回傳 ``{text}``,可帶 ``locale``。它以 MCP 工具
40+
``ac_format_message`` 以及 Script Builder 中 **Data** 分類下的命令提供。

‎docs/source/Zh/zh_index.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ AutoControl 所有功能的完整使用指南。
135135
doc/new_features/v110_features_doc
136136
doc/new_features/v111_features_doc
137137
doc/new_features/v112_features_doc
138+
doc/new_features/v113_features_doc
138139
doc/ocr_backends/ocr_backends_doc
139140
doc/observability/observability_doc
140141
doc/operations_layer/operations_layer_doc

‎je_auto_control/__init__.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@
237237
from je_auto_control.utils.bidi_check import is_balanced as is_bidi_balanced
238238
# Locale-aware list formatting ("A, B, and C") in the style of CLDR
239239
from je_auto_control.utils.list_format import format_list
240+
# ICU-lite MessageFormat (plural / select / selectordinal rendering)
241+
from je_auto_control.utils.message_format import (
242+
format_message, ordinal_category, plural_category,
243+
)
240244
# CI workflow annotations (GitHub Actions)
241245
from je_auto_control.utils.ci_annotations import (
242246
emit_annotations, format_annotation,
@@ -991,6 +995,9 @@ def start_autocontrol_gui(*args, **kwargs):
991995
"is_trojan_source",
992996
"strip_bidi_controls",
993997
"format_list",
998+
"format_message",
999+
"ordinal_category",
1000+
"plural_category",
9941001
"emit_annotations", "format_annotation",
9951002
"ClipboardHistory", "default_clipboard_history",
9961003
"analyze_heal_log", "heal_stats", "scan_secrets",

‎je_auto_control/gui/script_builder/command_schema.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2139,6 +2139,17 @@ def _add_resilience_specs(specs: List[CommandSpec]) -> None:
21392139
),
21402140
description="Join items into a localised list ('A, B, and C').",
21412141
))
2142+
specs.append(CommandSpec(
2143+
"AC_format_message", "Data", "Text: Format Message (ICU)",
2144+
fields=(
2145+
FieldSpec("pattern", FieldType.STRING,
2146+
placeholder="{count, plural, one {# item} other {# items}}"),
2147+
FieldSpec("args", FieldType.STRING, placeholder='{"count": 3}'),
2148+
FieldSpec("locale", FieldType.STRING, optional=True,
2149+
placeholder="en | fr"),
2150+
),
2151+
description="Render ICU plural/select/selectordinal message.",
2152+
))
21422153
specs.append(CommandSpec(
21432154
"AC_diff_rows", "Data", "Dataset Diff: Rows by Key",
21442155
fields=(

‎je_auto_control/utils/executor/action_executor.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3021,6 +3021,16 @@ def _format_list(items: Any, style: str = "and",
30213021
return {"text": format_list(list(items), style=style, locale=locale)}
30223022

30233023

3024+
def _format_message(pattern: str, args: Any = None,
3025+
locale: str = "en") -> Dict[str, Any]:
3026+
"""Adapter: render an ICU-lite MessageFormat pattern."""
3027+
import json
3028+
from je_auto_control.utils.message_format import format_message
3029+
if isinstance(args, str):
3030+
args = json.loads(args)
3031+
return {"text": format_message(pattern, args or {}, locale=locale)}
3032+
3033+
30243034
def _cas_put(name: str, key: str, value: Any,
30253035
expected_version: Any = None) -> Dict[str, Any]:
30263036
"""Adapter: optimistic put into a named versioned store."""
@@ -4711,6 +4721,7 @@ def __init__(self):
47114721
"AC_bidi_check": _bidi_check,
47124722
"AC_bidi_strip": _bidi_strip,
47134723
"AC_format_list": _format_list,
4724+
"AC_format_message": _format_message,
47144725
"AC_detect_drift": _detect_drift,
47154726
"AC_categorical_drift": _categorical_drift,
47164727
"AC_diff_rows": _diff_rows,

0 commit comments

Comments
 (0)