The es pair, and a dictionary that was quietly Spain's
Phase 25. Spanish was never built — the groundwork was all [x] (DreamDict data, the prompt language, the L1 rule gating, TTS env-discovery), which is why the plan read as though it had shipped. shippedPairs was the honest answer: the server had been refusing es on purpose. The langpack is neutral Latin American, chosen with the user: tú, ustedes, no vosotros, and the pan-American half of every vocabulary split. A vitest greps for the peninsular twins the way fr is greped for québécismes — including coger, which is not merely regional but obscene through most of Latin America. The dictionary is the story. Debian's hunspell-es symlinks twenty country codes to one file, which reads as pan-Hispanic; RLA publishes twenty-four builds per release, one per country plus a generic es that is the union, and Debian ships peninsular es_ES. The 58,622-form gap is essentially voseo, so the first version of this commit underlined vení and tenés as misspellings and called it a considered gap. The MUST_ACCEPT list was written to catch exactly that and structurally could not: it asserted the pan-Hispanic vocabulary, and every RLA variant carries the full pan-Hispanic vocabulary — only the paradigms are localised. The REP table cited as the second witness is shared by all builds too. Two independent-looking proofs, neither able to distinguish anything, agreeing with each other. The profile now demands what discriminates, each verified against the build it targets: voseo rejects es_ES and Debian, vosotros rejects es_MX, and arepa/chévere/bacán reject es_AR, which has both paradigms and would otherwise pass. 717,640 forms, 1.74 MB gzipped, 762 ms / 97 MB in a real nspell. fr and pt-PT rebuild byte-identical from their own upstream debs, so the shared script still means what it meant. Shipping the union is fr's call arrived at from the other side: coût and cout are both correct French, tienes and tenés are both correct Spanish. The dictionary holds every variety because underlining is all it can do; the copy picks a register because speaking requires one. Reviewed by four models at the usual >=2-of-4 threshold, 5 of 27 findings applied — one catching the bedtime proverb as fr's Qui dort dîne calqued into Spanish, gloss and all, which is the rule the fr header states. One below-threshold finding (a missing ¡, seen by 1 of 4 because an absent opening mark has no closing ! to look wrong against) was applied and turned into an assertion instead: the suite now rejects any native line that closes ? or ! without opening one. piper-es on es_MX-ald-medium, not the es_ES-davefx-medium the plan named — six of Piper's nine Spanish voices are peninsular, so the obvious pick was the pt-PT trap through a different door. go build/vet/test, tsc, vite, vitest 251/251. Not deployed, not seen in a browser, not read by a native speaker, and no es account exists.
This commit is contained in:
@@ -41,6 +41,13 @@ func TestSetPairLang(t *testing.T) {
|
||||
t.Fatalf("pair_lang = %q, want fr", u.PairLang)
|
||||
}
|
||||
|
||||
if err := users.SetPairLang("bob", "es"); err != nil {
|
||||
t.Fatalf("set es: %v", err)
|
||||
}
|
||||
if u, _ := users.Get("bob"); u.PairLang != "es" {
|
||||
t.Fatalf("pair_lang = %q, want es", u.PairLang)
|
||||
}
|
||||
|
||||
// And back — a writer who tries a pair and doesn't like it must be able to
|
||||
// return, which is the whole reason the picker exists.
|
||||
if err := users.SetPairLang("bob", "zh"); err != nil {
|
||||
@@ -56,10 +63,13 @@ func TestSetPairLang(t *testing.T) {
|
||||
func TestSetPairLangRejectsUnshippedPairs(t *testing.T) {
|
||||
_, users, _ := newStores(t)
|
||||
|
||||
// "es" is the real case here — the pair whose pack has not been written yet.
|
||||
// "pt-BR" is the near-miss that matters most: a Brazilian code must not be
|
||||
// quietly served European copy and a European voice.
|
||||
for _, lang := range []string{"es", "pt-BR", "fr-CA", "klingon", "", " "} {
|
||||
// The near-misses are the ones that matter, and there are two of them now.
|
||||
// "pt-BR" must not be quietly served European copy and a European voice;
|
||||
// "es-ES" is the same mistake pointing the other way, because the es pack is
|
||||
// deliberately Latin American and reads itself aloud in a Mexican voice. A
|
||||
// regional code Petal has not decided about is refused rather than rounded
|
||||
// to the nearest pack it happens to have.
|
||||
for _, lang := range []string{"es-ES", "pt-BR", "fr-CA", "de", "klingon", "", " "} {
|
||||
if err := users.SetPairLang("bob", lang); err == nil {
|
||||
t.Fatalf("stored unshipped pair %q", lang)
|
||||
}
|
||||
@@ -98,7 +108,7 @@ func TestUpdateMeHandlerRejects(t *testing.T) {
|
||||
_, users, _ := newStores(t)
|
||||
|
||||
for name, body := range map[string]string{
|
||||
"unshipped pair": `{"pair_lang":"es"}`,
|
||||
"unshipped pair": `{"pair_lang":"es-ES"}`,
|
||||
"missing field": `{}`,
|
||||
"not json": `pt-PT`,
|
||||
} {
|
||||
|
||||
@@ -76,8 +76,13 @@ func (u *UserStore) MeHandler() http.HandlerFunc {
|
||||
// this one names the pairs Petal can render itself in, which requires a langpack
|
||||
// on the frontend. Accepting a code with no pack would leave her looking at
|
||||
// Chinese with no way back except another guess, so the server refuses it. es
|
||||
// joins this list on the day its pack lands, not before.
|
||||
var shippedPairs = []string{"zh", "pt-PT", "fr"}
|
||||
// joined on the day its pack landed, not before.
|
||||
//
|
||||
// These four are now every pair PairLang names on the frontend, which makes the
|
||||
// two lists look redundant. They are not: the next pair will exist in the type
|
||||
// and in the prompts long before it has copy, and this list is the one that
|
||||
// says a writer may actually be sent there.
|
||||
var shippedPairs = []string{"zh", "pt-PT", "fr", "es"}
|
||||
|
||||
func pairIsShipped(lang string) bool {
|
||||
for _, p := range shippedPairs {
|
||||
|
||||
@@ -16,6 +16,8 @@ func TestTTSVoicesDiscovery(t *testing.T) {
|
||||
"TTS_VOICE_PT=pt_PT-tugão-medium",
|
||||
"TTS_ENDPOINT_FR=http://piper-fr:5000",
|
||||
"TTS_VOICE_FR=fr_FR-siwis-medium",
|
||||
"TTS_ENDPOINT_ES=http://piper-es:5000",
|
||||
"TTS_VOICE_ES=es_MX-ald-medium",
|
||||
// Noise that must not become a language.
|
||||
"TTS_PATH=/synthesize",
|
||||
"PATH=/usr/bin",
|
||||
@@ -30,6 +32,10 @@ func TestTTSVoicesDiscovery(t *testing.T) {
|
||||
// Phase 24's whole TTS change: a fourth language costs two lines here
|
||||
// and a compose service, and no Go at all.
|
||||
"fr": {"http://piper-fr:5000", "fr_FR-siwis-medium"},
|
||||
// And a fifth cost exactly the same, which is the claim actually being
|
||||
// tested. The voice is Mexican on purpose: the es pack is Latin
|
||||
// American, and es_ES-davefx-medium would read it in the wrong accent.
|
||||
"es": {"http://piper-es:5000", "es_MX-ald-medium"},
|
||||
}
|
||||
if len(voices) != len(want) {
|
||||
t.Fatalf("discovered %v, want %v", voices, want)
|
||||
|
||||
Reference in New Issue
Block a user