mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 02:36:01 -10:00
Bring in the finished Go rewrite (developed under the placeholder name "duo") as the v2 codebase. Code-only import — no upstream git history. Config- and plugin-driven coding agent: registry core, OpenAI-compatible providers, builtin tools, MCP (stdio + HTTP) plugins, permission + sandbox, custom slash commands, @-references, two-model collaboration. Renamed throughout: duo→reasonix, Duo→Reasonix, DUO→REASONIX — module path, imports, CLI name, cmd/ dirs, reasonix.toml, .reasonix/commands, and $REASONIX_* env prefixes. Kept the v2 MIT LICENSE (the Go project had none). Verified: go build ./... · go vet ./... · go test ./... all green; gofmt clean. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
157 lines
6.8 KiB
Go
157 lines
6.8 KiB
Go
// Package i18n holds the CLI's translatable strings and a small detection
|
|
// helper. Architecture: a single Messages struct of exported string fields
|
|
// (plain text or fmt format strings, suffix *Fmt flags the latter). Each
|
|
// language declares one Messages value in its own file. Call sites read
|
|
// i18n.M.SomeField; for parameterised messages they pass it to fmt.Sprintf.
|
|
//
|
|
// Adding a field requires updating every messages_*.go file — drift is caught
|
|
// at test time by TestCatalogsComplete via reflection, so a missing translation
|
|
// fails CI instead of surfacing as a blank line at runtime.
|
|
//
|
|
// Scope (v1): CLI surface only — welcome, init wizard, chat REPL banner, usage,
|
|
// user-facing CLI errors. System prompts, internal error wrappers, and agent
|
|
// runtime telemetry stay English so model behaviour and developer logs are
|
|
// language-stable.
|
|
package i18n
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Messages is the catalogue of translatable CLI strings. Plain fields are
|
|
// printed verbatim; *Fmt fields are fmt format strings the caller passes to
|
|
// fmt.Sprintf. Catalogue values do not include trailing newlines — call sites
|
|
// add framing whitespace, so the same field works wherever it appears.
|
|
type Messages struct {
|
|
// welcome / status screen
|
|
Subtitle string // tagline under the product name in the welcome box
|
|
WelcomeTitleFmt string // first-run box title — %s = product name (styled)
|
|
NoConfigYet string // first-run cue under the welcome box
|
|
StartingChatFmt string // "Starting %s…" before dropping into chat
|
|
SetKeyHint string // shown when key is missing after init
|
|
ConfigLabel string // "config" status row label
|
|
ModelsLabel string // "models" status row label
|
|
ConfigNotFound string // shown when no config file exists
|
|
ConfigErrorFmt string // "%s — error: %v" — config path + parse error
|
|
NoKey string // status dot — no API key set
|
|
Ready string // status dot — provider ready
|
|
GetStarted string // section title above numbered steps
|
|
StepScaffold string // step 1 desc — reasonix init
|
|
StepSetKey string // step 2 command label
|
|
StepSetKeyHint string // step 2 desc — env var hint
|
|
StepChatDesc string // reasonix chat step desc
|
|
StepRunDesc string // reasonix run step desc
|
|
HelpFooter string // dim footer linking to reasonix help
|
|
|
|
// chat REPL
|
|
ChatTip string // tip line under the chat banner
|
|
TurnCancelled string // shown when Ctrl-C aborts the in-flight turn but the chat keeps running
|
|
NoSessionToResume string // shown when --continue / --resume finds nothing
|
|
ResumeRequiresTTY string // shown when --resume runs piped instead of on a terminal
|
|
PickSessionLabel string // header on the --resume picker
|
|
|
|
// chat TUI status line / approval banner.
|
|
ChatStatusThinkingFmt string // "%s thinking… (%ds · <cancel hint>)" — %s = spinner, %d = elapsed s
|
|
ChatStatusIdle string // shortcuts hint when idle
|
|
ChatStatusPlanApproval string // shortcuts hint while a plan is pending
|
|
PlanApprovalPrompt string // one-line "plan above is ready" banner shown above the input
|
|
ChatStatusToolApproval string // shortcuts hint while a tool call awaits approval
|
|
ToolApprovalPromptFmt string // "Allow %s%s?" banner — %s = tool name, %s = subject (leading space, or empty)
|
|
|
|
// chat TUI slash commands.
|
|
SlashCompactDone string // "/compact" succeeded
|
|
SlashCompactFailed string // "/compact" errored, prefixed before the underlying error
|
|
SlashNewDone string // "/new" succeeded
|
|
SlashNewFailed string // "/new" errored
|
|
SlashUnavailable string // the command is configured off (no callback wired)
|
|
SlashUnknown string // shown when the user types an unrecognised "/cmd"
|
|
SlashHelp string // listed commands
|
|
SlashPromptEmpty string // an MCP prompt returned no text to send
|
|
SlashMCPNone string // /mcp when no MCP servers are connected
|
|
CompHintSlash string // key hint footer under the slash-command menu
|
|
CompHintFile string // key hint footer under the @ file/resource menu
|
|
|
|
// init wizard
|
|
SelectProvidersLabel string // multi-select label
|
|
EnterAPIKeysHeader string // header before the per-env-var prompts
|
|
MissingKeyIntro string // shown when re-running the key step on a configured setup
|
|
WroteFileFmt string // "Wrote %s" — used for reasonix.toml and .env both
|
|
SetupComplete string // success line at end of init
|
|
SetupCancelled string // shown when the user aborts the wizard
|
|
TryHintFmt string // "Try: %s" — %s = command to try (styled)
|
|
NextHint string // non-interactive post-write hint
|
|
ConfirmReconfigureFmt string // "%s already exists. Reconfigure and overwrite?"
|
|
KeepingExisting string // when the user declines to overwrite
|
|
NotOverwritingFmt string // non-interactive overwrite refusal
|
|
|
|
// top-level / runAgent
|
|
UnknownCommandFmt string // "unknown command %q"
|
|
UsageRunHint string // "usage: reasonix run [--model NAME] <task>"
|
|
ErrorPrefix string // "error:" — prefix for fatal-error output
|
|
WriteConfigErr string // "write config:" — prefix for write failure
|
|
WriteEnvErr string // "write .env:" — prefix for env-write failure
|
|
|
|
// selection menus
|
|
SelectOneHint string // "(↑/↓ · Enter · q to cancel)"
|
|
SelectManyHint string // "(↑/↓ · Space · Enter · q)"
|
|
|
|
// usage / help
|
|
UsageBody string // full multi-line help text
|
|
}
|
|
|
|
// M is the active catalogue. DetectLanguage replaces it; English is the
|
|
// default so any code path that runs before detection still has text.
|
|
var M = English
|
|
|
|
// DetectLanguage selects a catalogue from override (e.g. cfg.Language) or the
|
|
// environment and installs it as M. Returns the resolved tag ("en", "zh") so
|
|
// callers can log or expose it.
|
|
//
|
|
// Priority: override > REASONIX_LANG > LC_ALL > LC_MESSAGES > LANG > "en".
|
|
func DetectLanguage(override string) string {
|
|
for _, c := range append([]string{override}, envCandidates()...) {
|
|
if tag := normalize(c); tag != "" {
|
|
return setLanguage(tag)
|
|
}
|
|
}
|
|
return setLanguage("en")
|
|
}
|
|
|
|
func envCandidates() []string {
|
|
keys := []string{"REASONIX_LANG", "LC_ALL", "LC_MESSAGES", "LANG"}
|
|
out := make([]string, len(keys))
|
|
for i, k := range keys {
|
|
out[i] = os.Getenv(k)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func setLanguage(tag string) string {
|
|
switch tag {
|
|
case "zh":
|
|
M = Chinese
|
|
return "zh"
|
|
default:
|
|
M = English
|
|
return "en"
|
|
}
|
|
}
|
|
|
|
// normalize maps a locale string (e.g. "zh_CN.UTF-8", "zh-Hans-CN", "Chinese
|
|
// (China)") to a short tag this package knows about. Returns "" for empty or
|
|
// unrecognised input so DetectLanguage can fall through to the next candidate.
|
|
func normalize(s string) string {
|
|
s = strings.ToLower(strings.TrimSpace(s))
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
if strings.HasPrefix(s, "zh") || strings.Contains(s, "chinese") || strings.Contains(s, "中文") {
|
|
return "zh"
|
|
}
|
|
if strings.HasPrefix(s, "en") || strings.Contains(s, "english") {
|
|
return "en"
|
|
}
|
|
return ""
|
|
}
|