Files
prosolis 336cae93e0 Phase 19: the copy stops being hardcoded Mandarin
Every `中文 · English` string moves out of ~29 components into
web/src/i18n: one Pack type, a verbatim zh pack, and two ways to read
it — usePack() for components, pack() for the modules that build a line
when something happens rather than when something renders.

Anything with a value in it is a function on the pack rather than a
template at the call site, English pluralisation included: word order
isn't universal, and a pack author has to be able to move the number.
The roster constants (tones, rewrite styles, export formats, companions)
keep only value + emoji, so a label can't drift from its key.

On the server, internal/llm/lang.go replaces "Simplified Chinese" in the
three prompts that actually name her language. pt-PT is spelled
"European Portuguese (pt-PT, never Brazilian Portuguese)" in the prompt
itself, and each Lang carries her word for "why" so the tutor prompt
still recognises the question when she asks it her way.

pair_lang reaches the model through the row-scoped query each handler
already ran — the one that proves she owns the document — rather than a
second lookup that could disagree with it.

Also records Phase 18's deploy: migration 0011 rehearsed against a copy
of the live VPS database, then applied for real.
2026-07-27 08:37:05 -07:00

53 lines
2.3 KiB
Go

package llm
import "strings"
// The pair language, as the prompts need to talk about it.
//
// Three of Petal's prompts name the writer's first language rather than merely
// being written in English: the collocation coach asks for a gloss in it, Ask
// Petal offers to answer in it, and the explanation translator renders into it.
// Before Phase 19 all three said "Simplified Chinese" outright, which made the
// zh pair the only one that could ever work.
//
// A Lang is not a translation of the prompt — the instructions stay in English,
// which is what the model follows best. It is the name the model should use for
// her language, plus the one word it should watch for when she writes in it.
type Lang struct {
// Code matches users.pair_lang.
Code string
// Name is how the prompt refers to the language, spelled the way a model
// recognises it. Regional precision matters here: "European Portuguese" is
// not "Portuguese" to a model that has read far more pt-BR than pt-PT.
Name string
// Why asks the same thing she would ask in her own language. It goes into
// the Ask Petal prompt as an example, so a model that answers only to
// English "why" still recognises the question when she types it her way.
Why string
}
// langs holds every pair Petal can currently be a partner in. A language with a
// frontend langpack but no entry here still works — it falls back to zh's
// behaviour of the prompts, which is wrong but not broken — so keep the two in
// step when a pair ships.
var langs = map[string]Lang{
"zh": {Code: "zh", Name: "Simplified Chinese (Mandarin)", Why: "为什么"},
"pt-PT": {Code: "pt-PT", Name: "European Portuguese (pt-PT, never Brazilian Portuguese)", Why: "porquê"},
"fr": {Code: "fr", Name: "French", Why: "pourquoi"},
"es": {Code: "es", Name: "Spanish", Why: "por qué"},
}
// DefaultLang is the pair assumed when none is known — the column's default, and
// the only pair that existed before Phase 19.
var DefaultLang = langs["zh"]
// LangFor resolves a users.pair_lang value. An empty or unrecognised code falls
// back to the default rather than erroring: a prompt is not the place to
// discover a configuration problem, and the writing still has to be checked.
func LangFor(code string) Lang {
if l, ok := langs[strings.TrimSpace(code)]; ok {
return l
}
return DefaultLang
}