105 lines
3.1 KiB
Go
105 lines
3.1 KiB
Go
|
|
package ai
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"strings"
|
|||
|
|
"unicode"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type langCtxKey struct{}
|
|||
|
|
|
|||
|
|
// WithResponseLanguage attaches UI/locale language to ctx for system prompt injection.
|
|||
|
|
func WithResponseLanguage(ctx context.Context, lang string) context.Context {
|
|||
|
|
if ctx == nil {
|
|||
|
|
ctx = context.Background()
|
|||
|
|
}
|
|||
|
|
return context.WithValue(ctx, langCtxKey{}, NormalizeResponseLanguage(lang))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ResponseLanguageFrom returns zh-TW | en (default zh-TW).
|
|||
|
|
func ResponseLanguageFrom(ctx context.Context) string {
|
|||
|
|
if ctx == nil {
|
|||
|
|
return "zh-TW"
|
|||
|
|
}
|
|||
|
|
if v, ok := ctx.Value(langCtxKey{}).(string); ok && v != "" {
|
|||
|
|
return NormalizeResponseLanguage(v)
|
|||
|
|
}
|
|||
|
|
return "zh-TW"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NormalizeResponseLanguage maps preferred_language / Accept-Language → zh-TW | en.
|
|||
|
|
func NormalizeResponseLanguage(raw string) string {
|
|||
|
|
s := strings.ToLower(strings.TrimSpace(raw))
|
|||
|
|
if s == "" {
|
|||
|
|
return "zh-TW"
|
|||
|
|
}
|
|||
|
|
if i := strings.IndexAny(s, ",;"); i >= 0 {
|
|||
|
|
s = strings.TrimSpace(s[:i])
|
|||
|
|
}
|
|||
|
|
s = strings.ReplaceAll(s, "_", "-")
|
|||
|
|
if s == "en" || strings.HasPrefix(s, "en-") {
|
|||
|
|
return "en"
|
|||
|
|
}
|
|||
|
|
// zh / zh-TW / zh-Hant / zh-CN → 產品主語系繁中
|
|||
|
|
return "zh-TW"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SystemPromptForLanguage is always injected as the first system message before user content.
|
|||
|
|
// 只約束語言,不要綁「產品助手」人設——否則仿寫/產文會像客服而不是本人在發 Threads。
|
|||
|
|
func SystemPromptForLanguage(lang string) string {
|
|||
|
|
switch NormalizeResponseLanguage(lang) {
|
|||
|
|
case "en":
|
|||
|
|
return strings.TrimSpace(`
|
|||
|
|
Language rule (mandatory):
|
|||
|
|
- Write in natural English unless the user explicitly asks for another language.
|
|||
|
|
- For JSON/structured output, keep human-readable string values in English.
|
|||
|
|
- Do not add meta commentary (e.g. "As an AI…") unless asked.
|
|||
|
|
`)
|
|||
|
|
default:
|
|||
|
|
return strings.TrimSpace(`
|
|||
|
|
語言規則(強制):
|
|||
|
|
- 使用自然的「繁體中文(台灣用語)」,除非使用者明確要求其他語言。
|
|||
|
|
- 若要求 JSON/結構化輸出,給人看的字串也要用繁中。
|
|||
|
|
- 不要加「作為 AI…」「以下是分析…」這類 meta 前綴;直接完成任務。
|
|||
|
|
- 產文/仿寫時像真人滑手機在打字,不要像客服或文案機器人。
|
|||
|
|
`)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PickLanguage prefers member preferred_language, then Accept-Language header.
|
|||
|
|
func PickLanguage(preferredLanguage, acceptLanguage string) string {
|
|||
|
|
if strings.TrimSpace(preferredLanguage) != "" {
|
|||
|
|
return NormalizeResponseLanguage(preferredLanguage)
|
|||
|
|
}
|
|||
|
|
return NormalizeResponseLanguage(acceptLanguage)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// InferScriptLanguage 從文本統計漢字 vs 拉丁字母,推 zh-TW | en;信號不足回 ""。
|
|||
|
|
// 用於人設指紋/範例:英文人物應回英文,不該被 UI 語系或「繁中」硬規則蓋掉。
|
|||
|
|
func InferScriptLanguage(texts ...string) string {
|
|||
|
|
var han, latin int
|
|||
|
|
for _, t := range texts {
|
|||
|
|
for _, r := range t {
|
|||
|
|
switch {
|
|||
|
|
case unicode.Is(unicode.Han, r):
|
|||
|
|
han++
|
|||
|
|
case (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z'):
|
|||
|
|
latin++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
total := han + latin
|
|||
|
|
if total < 16 {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
// 拉丁明顯佔優 → 英文
|
|||
|
|
if latin >= han*2 && latin >= 16 {
|
|||
|
|
return "en"
|
|||
|
|
}
|
|||
|
|
// 漢字夠多 → 繁中
|
|||
|
|
if han >= 12 && han >= latin {
|
|||
|
|
return "zh-TW"
|
|||
|
|
}
|
|||
|
|
return ""
|
|||
|
|
}
|