From 377330a6b00bc00ea73d47b01c70971225faea59 Mon Sep 17 00:00:00 2001 From: Ilia Sidorenko Date: Fri, 29 May 2026 13:36:20 -0400 Subject: [PATCH] widen `--lang` whitelist to the full server-accepted language set --- CHANGELOG.md | 1 + internal/api/languages_gen.go | 97 ++++++++++++++++++++++++++++++++++ internal/api/languages_test.go | 20 +++---- 3 files changed, 104 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8326d6..57c71fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- The `--lang` whitelist now covers the full set of STQRY-supported content languages, generated from the server's accepted set rather than a hand-maintained subset, so any language the platform accepts is no longer rejected client-side. - `stqry quizzes` — full CRUD for the Quizzes Public API across the quiz → questions → answers hierarchy, with matching MCP tools, shell completion, and reference/coverage docs. Translated fields respect `--lang`, `--question-type` is validated client-side, answers carry a `--correct` flag, and `delete`/`remove` accept `--lang` for per-locale translation deletes. ### Fixed diff --git a/internal/api/languages_gen.go b/internal/api/languages_gen.go index 1019371..f149d47 100644 --- a/internal/api/languages_gen.go +++ b/internal/api/languages_gen.go @@ -1,4 +1,9 @@ // Code generated; DO NOT EDIT. +// +// Source of truth: the full set of content languages the server accepts. The +// server defines a translation accessor for every such code, so the CLI mirrors +// the full set rather than a narrower subset. Regenerate when the platform adds +// a language. package api @@ -11,26 +16,61 @@ type Language struct { // SupportedLanguages lists every language code that STQRY content // endpoints accept. var SupportedLanguages = []Language{ + {Code: "af", Name: "Afrikaans"}, + {Code: "ak", Name: "Akan"}, {Code: "alq", Name: "Algonquin"}, + {Code: "am", Name: "Amharic"}, {Code: "ar", Name: "Arabic"}, + {Code: "as", Name: "Azerbaijani"}, + {Code: "ay", Name: "Aymara"}, + {Code: "az", Name: "Azerbaijani"}, + {Code: "be", Name: "Belarusian"}, {Code: "bg", Name: "Bulgarian"}, + {Code: "bho", Name: "Bhojpuri"}, + {Code: "bm", Name: "Bambara"}, + {Code: "bn", Name: "Bengali"}, + {Code: "bs", Name: "Bosnian"}, {Code: "buc", Name: "Kibushi"}, {Code: "ca", Name: "Catalan"}, + {Code: "ceb", Name: "Cebuano"}, + {Code: "ckb", Name: "Central Kurdish"}, + {Code: "co", Name: "Cornish"}, {Code: "cs", Name: "Czech"}, {Code: "cy", Name: "Welsh"}, {Code: "da", Name: "Danish"}, {Code: "de", Name: "German"}, + {Code: "doi", Name: "Dogri"}, + {Code: "dv", Name: "Maldivian"}, + {Code: "ee", Name: "Ewe"}, {Code: "el", Name: "Greek"}, {Code: "en", Name: "English (US)"}, + {Code: "en-AU", Name: "English (Australian)"}, + {Code: "en-CA", Name: "English (Canadian)"}, {Code: "en-GB", Name: "English (United Kingdom)"}, + {Code: "en-IN", Name: "English (Indian)"}, + {Code: "en-SG", Name: "English (Singapore)"}, + {Code: "eo", Name: "Esperanto"}, {Code: "es", Name: "Spanish"}, + {Code: "es-419", Name: "Spanish (Latin America)"}, + {Code: "es-MX", Name: "Spanish (Mexico)"}, + {Code: "es-US", Name: "Spanish (United States)"}, + {Code: "et", Name: "Estonian"}, {Code: "eu", Name: "Basque"}, {Code: "fa", Name: "Persian"}, {Code: "fi", Name: "Finnish"}, + {Code: "fil", Name: "Filipino"}, {Code: "fj", Name: "Fijian"}, {Code: "fo", Name: "Faroese"}, {Code: "fr", Name: "French"}, + {Code: "fr-CA", Name: "French (Canada)"}, + {Code: "fy", Name: "Western Frisian"}, {Code: "ga", Name: "Irish"}, + {Code: "gd", Name: "Gaelic; Scottish Gaelic"}, + {Code: "gl", Name: "Galician"}, + {Code: "gn", Name: "Guarani"}, + {Code: "gom", Name: "Goan Konkani gom"}, + {Code: "gu", Name: "Gujarati"}, + {Code: "ha", Name: "Hausa"}, {Code: "haw", Name: "Hawaiian"}, {Code: "he", Name: "Hebrew"}, {Code: "hi", Name: "Hindi"}, @@ -40,47 +80,104 @@ var SupportedLanguages = []Language{ {Code: "hu", Name: "Hungarian"}, {Code: "hy", Name: "Armenian"}, {Code: "id", Name: "Indonesian"}, + {Code: "ig", Name: "Igbo"}, + {Code: "ilo", Name: "Iloko"}, {Code: "is", Name: "Icelandic"}, {Code: "it", Name: "Italian"}, {Code: "iu", Name: "Inuktitut"}, + {Code: "iw", Name: "Hebrew"}, {Code: "ja", Name: "Japanese"}, + {Code: "jv", Name: "Javanese"}, + {Code: "jw", Name: ""}, {Code: "ka", Name: "Georgian"}, {Code: "kk", Name: "Kazakh"}, + {Code: "km", Name: "Khmer"}, + {Code: "kn", Name: "Kannada"}, {Code: "ko", Name: "Korean (South Korea)"}, + {Code: "kri", Name: "Krio"}, + {Code: "ku", Name: "Kurdish"}, + {Code: "ky", Name: "Kyrgyz"}, + {Code: "la", Name: "Latin"}, + {Code: "lb", Name: "Luxembourgish; Letzeburgesch"}, + {Code: "lg", Name: "Ganda"}, {Code: "lkt", Name: "Lakota"}, {Code: "lld", Name: "Ladin"}, + {Code: "ln", Name: "Lingala"}, {Code: "lo", Name: "Lao"}, {Code: "lt", Name: "Lithuanian"}, + {Code: "lus", Name: "Lushai"}, {Code: "lv", Name: "Latvian"}, + {Code: "mai", Name: "Maithili"}, + {Code: "mg", Name: "Malagasy"}, {Code: "mi", Name: "Māori"}, + {Code: "mk", Name: "Macedonian"}, + {Code: "ml", Name: "Malayalam"}, + {Code: "mn", Name: "Mongolian"}, + {Code: "mni-Mtei", Name: "Meitei"}, + {Code: "mr", Name: "Marathi"}, {Code: "ms", Name: "Malay"}, + {Code: "mt", Name: "Maltese"}, + {Code: "my", Name: "Burmese"}, {Code: "ne", Name: "Nepali"}, {Code: "nl", Name: "Dutch"}, {Code: "no", Name: "Norwegian"}, + {Code: "nso", Name: "Northern Sotho"}, + {Code: "ny", Name: "Chichewa; Chewa; Nyanja"}, {Code: "oji", Name: "Ojibwe"}, + {Code: "om", Name: "Oromo"}, + {Code: "or", Name: "Oriya"}, {Code: "pa", Name: "Panjabi; Punjabi"}, {Code: "pap", Name: "Papiamentu"}, {Code: "pl", Name: "Polish"}, + {Code: "ps", Name: "Pushto; Pashto"}, {Code: "pt", Name: "Portuguese (Portugal)"}, {Code: "pt-BR", Name: "Portuguese (Brazil)"}, + {Code: "qu", Name: "Quechua"}, + {Code: "rm", Name: "Romansh"}, {Code: "ro", Name: "Romanian"}, {Code: "ru", Name: "Russian"}, + {Code: "rw", Name: "Kinyarwanda"}, + {Code: "sa", Name: "Sanskrit"}, + {Code: "sd", Name: "Sindhi"}, {Code: "see", Name: "Seneca"}, + {Code: "si", Name: "Sinhala"}, + {Code: "sk", Name: "Slovak"}, + {Code: "sl", Name: "Slovenian"}, {Code: "sm", Name: "Samoan"}, + {Code: "sn", Name: "Shona"}, {Code: "so", Name: "Somali"}, + {Code: "sq", Name: "Albanian"}, + {Code: "sr", Name: "Serbian"}, {Code: "st", Name: "Sotho, Southern"}, + {Code: "su", Name: "Sundanese"}, {Code: "sv", Name: "Swedish"}, + {Code: "sw", Name: "Swahili"}, {Code: "swb", Name: "Shimaore"}, {Code: "ta", Name: "Tamil"}, + {Code: "te", Name: "Telugu"}, + {Code: "tg", Name: "Tajik"}, {Code: "th", Name: "Thai"}, + {Code: "ti", Name: "Tigrinya"}, + {Code: "tk", Name: "Turkmen"}, {Code: "tl", Name: "Tagalog"}, {Code: "tn", Name: "Tswana"}, {Code: "to", Name: "Tongan"}, {Code: "tr", Name: "Turkish"}, + {Code: "ts", Name: "Tsonga"}, + {Code: "tt", Name: "Tatar"}, + {Code: "ug", Name: "Uighur; Uyghur"}, {Code: "uk", Name: "Ukrainian"}, + {Code: "ur", Name: "Urdu"}, + {Code: "uz", Name: "Uzbek"}, {Code: "vi", Name: "Vietnamese"}, {Code: "xh", Name: "Xhosa"}, + {Code: "yi", Name: "Yiddish"}, + {Code: "yo", Name: "Yoruba"}, + {Code: "zh", Name: "Chinese"}, + {Code: "zh-CN", Name: "Chinese (Simplified)"}, {Code: "zh-Hans", Name: "Chinese (Simplified)"}, {Code: "zh-Hant", Name: "Chinese (Traditional)"}, + {Code: "zh-HK", Name: "Chinese (Hong Kong)"}, + {Code: "zh-TW", Name: "Chinese (Traditional)"}, {Code: "zu", Name: "Zulu"}, } diff --git a/internal/api/languages_test.go b/internal/api/languages_test.go index e35bbca..516fcc9 100644 --- a/internal/api/languages_test.go +++ b/internal/api/languages_test.go @@ -12,26 +12,18 @@ func TestValidateLanguage_Empty(t *testing.T) { } func TestValidateLanguage_Supported(t *testing.T) { - for _, code := range []string{"en", "fr", "zh-Hans", "zh-Hant", "pt-BR", "en-GB"} { + // The whitelist mirrors the server's full set of accepted content + // languages, not a narrower subset — the server defines a translation + // accessor for every such code, so it accepts them all. This previously + // rejected codes like `hr`, `sk`, `sl`, `sr`, `bs`, `zh` that the + // platform happily writes. + for _, code := range []string{"en", "fr", "zh", "zh-Hans", "zh-Hant", "pt-BR", "en-GB", "hr", "sk", "sl", "sr", "bs"} { if err := ValidateLanguage(code); err != nil { t.Errorf("%q should be supported: %v", code, err) } } } -func TestValidateLanguage_RejectsBareZh(t *testing.T) { - // The reason this whole feature exists: 'zh' alone is not a STQRY content - // language and silently writing to it created orphan rows. - err := ValidateLanguage("zh") - if err == nil { - t.Fatal("'zh' should be rejected") - } - msg := err.Error() - if !strings.Contains(msg, "zh-Hans") && !strings.Contains(msg, "zh-Hant") { - t.Errorf("expected suggestion of zh-Hans or zh-Hant, got: %s", msg) - } -} - func TestValidateLanguage_Suggestion(t *testing.T) { cases := map[string]string{ "En": "en", // case mismatch on a known code