Spaces:
Paused
Paused
| package main | |
| import ( | |
| "context" | |
| "errors" | |
| "fmt" | |
| "strings" | |
| pi "github.com/joshp123/pi-golang" | |
| ) | |
| type PiTranslator struct { | |
| client *pi.OneShotClient | |
| } | |
| func NewPiTranslator(srcLang, tgtLang string, glossary []GlossaryEntry) (*PiTranslator, error) { | |
| options := pi.DefaultOneShotOptions() | |
| options.AppName = "openclaw-docs-i18n" | |
| options.Mode = pi.ModeDragons | |
| options.Dragons = pi.DragonsOptions{ | |
| Provider: "anthropic", | |
| Model: modelVersion, | |
| Thinking: "high", | |
| } | |
| options.SystemPrompt = translationPrompt(srcLang, tgtLang, glossary) | |
| client, err := pi.StartOneShot(options) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return &PiTranslator{client: client}, nil | |
| } | |
| func (t *PiTranslator) Translate(ctx context.Context, text, srcLang, tgtLang string) (string, error) { | |
| if t.client == nil { | |
| return "", errors.New("pi client unavailable") | |
| } | |
| prefix, core, suffix := splitWhitespace(text) | |
| if core == "" { | |
| return text, nil | |
| } | |
| state := NewPlaceholderState(core) | |
| placeholders := make([]string, 0, 8) | |
| mapping := map[string]string{} | |
| masked := maskMarkdown(core, state.Next, &placeholders, mapping) | |
| res, err := t.client.Run(ctx, masked) | |
| if err != nil { | |
| return "", err | |
| } | |
| translated := strings.TrimSpace(res.Text) | |
| if err := validatePlaceholders(translated, placeholders); err != nil { | |
| return "", err | |
| } | |
| translated = unmaskMarkdown(translated, placeholders, mapping) | |
| return prefix + translated + suffix, nil | |
| } | |
| func (t *PiTranslator) Close() { | |
| if t.client != nil { | |
| _ = t.client.Close() | |
| } | |
| } | |
| func translationPrompt(srcLang, tgtLang string, glossary []GlossaryEntry) string { | |
| srcLabel := srcLang | |
| tgtLabel := tgtLang | |
| if strings.EqualFold(srcLang, "en") { | |
| srcLabel = "English" | |
| } | |
| if strings.EqualFold(tgtLang, "zh-CN") { | |
| tgtLabel = "Simplified Chinese" | |
| } | |
| glossaryBlock := buildGlossaryPrompt(glossary) | |
| return strings.TrimSpace(fmt.Sprintf(`You are a translation function, not a chat assistant. | |
| Translate from %s to %s. | |
| Rules: | |
| - Output ONLY the translated text. No preamble, no questions, no commentary. | |
| - Preserve Markdown syntax exactly (headings, lists, tables, emphasis). | |
| - Do not translate code spans/blocks, config keys, CLI flags, or env vars. | |
| - Do not alter URLs or anchors. | |
| - Preserve placeholders exactly: __OC_I18N_####__. | |
| - Use neutral technical Chinese; avoid slang or jokes. | |
| - Keep product names in English: OpenClaw, Gateway, Pi, WhatsApp, Telegram, Discord, iMessage, Slack, Microsoft Teams, Google Chat, Signal. | |
| %s | |
| If the input is empty, output empty. | |
| If the input contains only placeholders, output it unchanged.`, srcLabel, tgtLabel, glossaryBlock)) | |
| } | |
| func buildGlossaryPrompt(glossary []GlossaryEntry) string { | |
| if len(glossary) == 0 { | |
| return "" | |
| } | |
| var lines []string | |
| lines = append(lines, "Preferred translations (use when natural):") | |
| for _, entry := range glossary { | |
| if entry.Source == "" || entry.Target == "" { | |
| continue | |
| } | |
| lines = append(lines, fmt.Sprintf("- %s -> %s", entry.Source, entry.Target)) | |
| } | |
| return strings.Join(lines, "\n") | |
| } | |