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 ""
|
||
}
|