Skip to content

Commit c1d262f

Browse files
authored
Add monthly notification language support (#85)
1 parent 6569435 commit c1d262f

3 files changed

Lines changed: 81 additions & 12 deletions

File tree

.github/workflows/monthly_publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
PUBLISH_MODE: ${{ vars.PUBLISH_MODE || 'core_major' }}
2323
RELEASE_CHANNEL: production
2424
PRODUCTION_PROFILE: binance_only_core_major_monthly
25+
NOTIFY_LANG: ${{ vars.NOTIFY_LANG || 'en' }}
2526
DOWNLOAD_TOP_LIQUID: ${{ vars.DOWNLOAD_TOP_LIQUID || '90' }}
2627
FIRESTORE_COLLECTION: ${{ vars.FIRESTORE_COLLECTION || 'strategy' }}
2728
FIRESTORE_DOCUMENT: ${{ vars.FIRESTORE_DOCUMENT || 'CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL' }}

scripts/run_monthly_build_telegram.py

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,55 @@
1313

1414
PROJECT_ROOT = Path(__file__).resolve().parents[1]
1515
DEFAULT_OUTPUT_DIR = PROJECT_ROOT / "data" / "output"
16+
DEFAULT_NOTIFY_LANG = "en"
17+
SUPPORTED_NOTIFY_LANGS = {"en", "zh"}
18+
19+
_TEXTS = {
20+
"en": {
21+
"title": "CryptoLivePoolPipelines monthly release",
22+
"status": "status",
23+
"status_ok": "ok",
24+
"status_warning": "warning",
25+
"as_of_date": "as_of_date",
26+
"official": "official",
27+
"shadow": "shadow",
28+
"shadow_not_generated": "not_generated_in_this_run",
29+
"manifest": "manifest",
30+
"present": "present",
31+
"missing": "missing",
32+
"validation": "validation",
33+
"warnings": "warnings",
34+
"none": "none",
35+
},
36+
"zh": {
37+
"title": "CryptoLivePoolPipelines 月度发布",
38+
"status": "状态",
39+
"status_ok": "正常",
40+
"status_warning": "警告",
41+
"as_of_date": "日期",
42+
"official": "官方池",
43+
"shadow": "影子池",
44+
"shadow_not_generated": "本轮未生成",
45+
"manifest": "发布清单",
46+
"present": "已存在",
47+
"missing": "缺失",
48+
"validation": "校验",
49+
"warnings": "告警",
50+
"none": "无",
51+
},
52+
}
53+
54+
55+
def get_notify_lang(value: str | None = None) -> str:
56+
raw = str(value if value is not None else os.getenv("NOTIFY_LANG", DEFAULT_NOTIFY_LANG)).strip().lower()
57+
if raw in SUPPORTED_NOTIFY_LANGS:
58+
return raw
59+
return DEFAULT_NOTIFY_LANG
60+
61+
62+
def text(lang: str, key: str) -> str:
63+
active_lang = lang if lang in SUPPORTED_NOTIFY_LANGS else DEFAULT_NOTIFY_LANG
64+
return _TEXTS.get(active_lang, _TEXTS[DEFAULT_NOTIFY_LANG]).get(key, _TEXTS[DEFAULT_NOTIFY_LANG].get(key, key))
1665

1766

1867
def parse_args() -> argparse.Namespace:
@@ -34,6 +83,11 @@ def parse_args() -> argparse.Namespace:
3483
default="",
3584
help="Optional path to write the rendered message for bundle packaging.",
3685
)
86+
parser.add_argument(
87+
"--lang",
88+
default="",
89+
help="Notification language override. Supported values: en, zh. Defaults to NOTIFY_LANG or en.",
90+
)
3791
return parser.parse_args()
3892

3993

@@ -146,42 +200,44 @@ def build_health_payload(output_dir: Path | str) -> dict[str, Any]:
146200
}
147201

148202

149-
def format_message(payload: dict[str, Any]) -> str:
203+
def format_message(payload: dict[str, Any], *, lang: str | None = None) -> str:
204+
active_lang = get_notify_lang(lang)
150205
official = payload["official_baseline"]
151206
manifest = payload["manifest"]
152207
validation = payload["validation"]
153208
shadow = payload["shadow_tracks"]
154209
shadow_line = (
155-
"shadow: "
210+
f"{text(active_lang, 'shadow')}: "
156211
f"official_baseline last={shadow['official_baseline']['last_as_of_date']} "
157212
f"releases={shadow['official_baseline']['release_count']}; "
158213
f"challenger_topk_60 last={shadow['challenger_topk_60']['last_as_of_date']} "
159214
f"releases={shadow['challenger_topk_60']['release_count']}"
160215
if shadow["official_baseline"]["available"] or shadow["challenger_topk_60"]["available"]
161-
else "shadow: not_generated_in_this_run"
216+
else f"{text(active_lang, 'shadow')}: {text(active_lang, 'shadow_not_generated')}"
162217
)
218+
status_key = "status_ok" if payload["status"] == "ok" else "status_warning"
163219
lines = [
164-
"CryptoLivePoolPipelines monthly release",
165-
f"status: {payload['status']}",
166-
f"as_of_date: {payload['as_of_date']}",
220+
text(active_lang, "title"),
221+
f"{text(active_lang, 'status')}: {text(active_lang, status_key)}",
222+
f"{text(active_lang, 'as_of_date')}: {payload['as_of_date']}",
167223
(
168-
"official: "
224+
f"{text(active_lang, 'official')}: "
169225
f"profile={official['profile']} "
170226
f"version={official['version']} "
171227
f"mode={official['mode']} "
172228
f"pool_size={official['pool_size']}"
173229
),
174230
shadow_line,
175-
f"manifest: {'present' if manifest['present'] else 'missing'} dry_run={manifest['dry_run']}",
231+
f"{text(active_lang, 'manifest')}: {text(active_lang, 'present') if manifest['present'] else text(active_lang, 'missing')} dry_run={manifest['dry_run']}",
176232
]
177233
if validation["ok"] is not None:
178234
lines.append(
179-
f"validation: ok={validation['ok']} manifest_present={validation['manifest_present']} age_days={validation['age_days']}"
235+
f"{text(active_lang, 'validation')}: ok={validation['ok']} manifest_present={validation['manifest_present']} age_days={validation['age_days']}"
180236
)
181237
if payload["warnings"]:
182-
lines.append("warnings: " + " | ".join(payload["warnings"][:4]))
238+
lines.append(f"{text(active_lang, 'warnings')}: " + " | ".join(payload["warnings"][:4]))
183239
else:
184-
lines.append("warnings: none")
240+
lines.append(f"{text(active_lang, 'warnings')}: {text(active_lang, 'none')}")
185241
return "\n".join(lines)
186242

187243

@@ -201,7 +257,7 @@ def send_telegram_message(token: str, chat_id: str, message: str) -> None:
201257
def main() -> None:
202258
args = parse_args()
203259
payload = build_health_payload(args.output_dir)
204-
message = format_message(payload)
260+
message = format_message(payload, lang=getattr(args, "lang", ""))
205261
print(message)
206262
if args.output_path:
207263
output_path = Path(args.output_path)

tests/test_monthly_build_telegram.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,18 @@ def test_build_health_payload_allows_release_status_only_outputs(self) -> None:
110110
self.assertFalse(payload["shadow_tracks"]["official_baseline"]["available"])
111111
self.assertIn("shadow: not_generated_in_this_run", message)
112112

113+
def test_format_message_uses_chinese_when_requested(self) -> None:
114+
with tempfile.TemporaryDirectory() as tmp_dir:
115+
root = Path(tmp_dir)
116+
self.write_fixture_files(root)
117+
payload = MODULE.build_health_payload(root / "data" / "output")
118+
message = MODULE.format_message(payload, lang="zh")
119+
120+
self.assertIn("CryptoLivePoolPipelines 月度发布", message)
121+
self.assertIn("状态: 正常", message)
122+
self.assertIn("官方池:", message)
123+
self.assertIn("告警: 无", message)
124+
113125
def test_main_uses_global_telegram_chat_id(self) -> None:
114126
with tempfile.TemporaryDirectory() as tmp_dir:
115127
root = Path(tmp_dir)

0 commit comments

Comments
 (0)