feat(ai): localize hitl Discord messages via HITL_LANG (en, ja)

Discord に投稿する提案・エスカレーション・期限切れ・ボタンの文言を
言語セット(en / ja)に分離し、HITL_LANG で起動時に選択できるようにした。
識別子(action id、ラベル、run id、JSON)は言語に関係なくそのまま出す。
This commit is contained in:
seon committed 2026-10-08 14:33:48 +09:00
1 parent 8b403c54df
commit af1cb4f6dc
3 files changed
+131 -33

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
package hitl
import "fmt"
// Strings holds every user-facing text of the bot. The language is chosen once at start (HITL_LANG=en|ja).
// Identifiers (action ids, labels, run ids, JSON) stay as they are; only the surrounding text changes.
type Strings struct {
ProposalTitle func(actionID string) string
FieldRootCause string
FieldTarget string
FieldEvidence string
FieldRationale string
FieldRun string
FieldStatus string
FieldResult string
FieldAlert string
RootCauseValue func(label string, conf float64) string
ButtonApprove string
ButtonReject string
StatusWaiting func(unixTS int64) string
StatusRejected func(by string) string
StatusExecuting func(by string) string
StatusExecuted func(by string) string
StatusApproveFailed func(err string) string
StatusExecFailed func(by, err string) string
StatusExpiredBefore string
StatusNoDecision string
ExpiryMessage func(actionID, target, runID, scenarioID string) string
EscalationTitle func(label string) string
EscalationStatus string
ThreadRunbook string
NotAllowed string
Footer func(model, prompt, session string) string
Empty string
}
var langs = map[string]Strings{
"en": {
ProposalTitle: func(a string) string { return "Layer-2 proposal: " + a },
FieldRootCause: "Root cause", FieldTarget: "Target", FieldEvidence: "Evidence", FieldRationale: "Rationale",
FieldRun: "Run / scenario", FieldStatus: "Status", FieldResult: "Result", FieldAlert: "Alert",
RootCauseValue: func(l string, c float64) string { return fmt.Sprintf("%s (confidence %.2f)", l, c) },
ButtonApprove: "Approve", ButtonReject: "Reject",
StatusWaiting: func(t int64) string { return fmt.Sprintf("waiting for approval until <t:%d:T>", t) },
StatusRejected: func(by string) string { return "rejected by " + by },
StatusExecuting: func(by string) string { return "approved by " + by + ", executing..." },
StatusExecuted: func(by string) string { return "approved by " + by + "; executed" },
StatusApproveFailed: func(e string) string { return "approval failed: " + e },
StatusExecFailed: func(by, e string) string { return "approved by " + by + "; execution failed: " + e },
StatusExpiredBefore: "expired before approval",
StatusNoDecision: "no decision within the window; moved to layer 3",
ExpiryMessage: func(a, t, r, s string) string {
return fmt.Sprintf("Proposal `%s` for `%s` (run %s, scenario %s) received no decision within the approval window. Layer 3: follow the runbook or call the harness `/actions/execute` with the operator token.", a, t, r, s)
},
EscalationTitle: func(l string) string { return "Escalation: " + l },
EscalationStatus: "layer 3: human decision required",
ThreadRunbook: "Runbook:\n",
NotAllowed: "You are not in the approver role.",
Footer: func(m, p, s string) string { return fmt.Sprintf("model %s, prompt %s, session %s", m, p, s) },
Empty: "-",
},
"ja": {
ProposalTitle: func(a string) string { return "第2層の提案: " + a },
FieldRootCause: "推定原因", FieldTarget: "対象", FieldEvidence: "根拠", FieldRationale: "判断理由",
FieldRun: "Run / シナリオ", FieldStatus: "状態", FieldResult: "実行結果", FieldAlert: "アラート",
RootCauseValue: func(l string, c float64) string { return fmt.Sprintf("%s(確信度 %.2f)", l, c) },
ButtonApprove: "承認", ButtonReject: "却下",
StatusWaiting: func(t int64) string { return fmt.Sprintf("承認待ち(期限 <t:%d:T>)", t) },
StatusRejected: func(by string) string { return by + " が却下" },
StatusExecuting: func(by string) string { return by + " が承認、実行中..." },
StatusExecuted: func(by string) string { return by + " が承認、実行済み" },
StatusApproveFailed: func(e string) string { return "承認の記録に失敗: " + e },
StatusExecFailed: func(by, e string) string { return by + " が承認したが実行に失敗: " + e },
StatusExpiredBefore: "承認前に期限切れ",
StatusNoDecision: "期限内に判断がなかったため第3層へ引き継ぎ",
ExpiryMessage: func(a, t, r, s string) string {
return fmt.Sprintf("提案 `%s`(対象 `%s`、run %s、シナリオ %s)は承認期限内に判断されませんでした。第3層の対応です。ランブックに従って手動で対処するか、オペレーター用トークンでハーネスの `/actions/execute` を呼び出してください。", a, t, r, s)
},
EscalationTitle: func(l string) string { return "エスカレーション: " + l },
EscalationStatus: "第3層: 人の判断が必要",
ThreadRunbook: "ランブック:\n",
NotAllowed: "承認ロールが付与されていません。",
Footer: func(m, p, s string) string {
return fmt.Sprintf("モデル %s、プロンプト %s、セッション %s", m, p, s)
},
Empty: "-",
},
}
// Lang returns the string set for a language code; unknown codes fall back to English.
func Lang(code string) Strings {
if s, ok := langs[code]; ok {
return s
}
return langs["en"]
}