diff --git a/internal/language/language.go b/internal/language/language.go new file mode 100644 index 0000000..a821a4f --- /dev/null +++ b/internal/language/language.go @@ -0,0 +1,120 @@ +package language + +// Language represents a supported transcription language +type Language struct { + Code string // ISO 639-1 code (e.g., "en", "es", "zh") + Name string // English name (e.g., "English", "Spanish") + NativeName string // Native name (e.g., "English", "Espanol", "中文") +} + +// Auto represents auto-detection - used when user doesn't specify a language +var Auto = Language{Code: "", Name: "Auto-detect", NativeName: ""} + +// languages is the master list of supported languages +// derived from OpenAI Whisper's 57 supported languages +var languages = []Language{ + {Code: "af", Name: "Afrikaans", NativeName: "Afrikaans"}, + {Code: "ar", Name: "Arabic", NativeName: "العربية"}, + {Code: "hy", Name: "Armenian", NativeName: "Հdelays"}, + {Code: "az", Name: "Azerbaijani", NativeName: "Azərbaycan"}, + {Code: "be", Name: "Belarusian", NativeName: "Беларуская"}, + {Code: "bs", Name: "Bosnian", NativeName: "Bosanski"}, + {Code: "bg", Name: "Bulgarian", NativeName: "Български"}, + {Code: "ca", Name: "Catalan", NativeName: "Català"}, + {Code: "zh", Name: "Chinese", NativeName: "中文"}, + {Code: "hr", Name: "Croatian", NativeName: "Hrvatski"}, + {Code: "cs", Name: "Czech", NativeName: "Čeština"}, + {Code: "da", Name: "Danish", NativeName: "Dansk"}, + {Code: "nl", Name: "Dutch", NativeName: "Nederlands"}, + {Code: "en", Name: "English", NativeName: "English"}, + {Code: "et", Name: "Estonian", NativeName: "Eesti"}, + {Code: "fi", Name: "Finnish", NativeName: "Suomi"}, + {Code: "fr", Name: "French", NativeName: "Français"}, + {Code: "gl", Name: "Galician", NativeName: "Galego"}, + {Code: "de", Name: "German", NativeName: "Deutsch"}, + {Code: "el", Name: "Greek", NativeName: "Ελληνικά"}, + {Code: "he", Name: "Hebrew", NativeName: "עברית"}, + {Code: "hi", Name: "Hindi", NativeName: "हिन्दी"}, + {Code: "hu", Name: "Hungarian", NativeName: "Magyar"}, + {Code: "is", Name: "Icelandic", NativeName: "Íslenska"}, + {Code: "id", Name: "Indonesian", NativeName: "Bahasa Indonesia"}, + {Code: "it", Name: "Italian", NativeName: "Italiano"}, + {Code: "ja", Name: "Japanese", NativeName: "日本語"}, + {Code: "kn", Name: "Kannada", NativeName: "ಕನ್ನಡ"}, + {Code: "kk", Name: "Kazakh", NativeName: "Қазақ"}, + {Code: "ko", Name: "Korean", NativeName: "한국어"}, + {Code: "lv", Name: "Latvian", NativeName: "Latviešu"}, + {Code: "lt", Name: "Lithuanian", NativeName: "Lietuvių"}, + {Code: "mk", Name: "Macedonian", NativeName: "Македонски"}, + {Code: "ms", Name: "Malay", NativeName: "Bahasa Melayu"}, + {Code: "mr", Name: "Marathi", NativeName: "मराठी"}, + {Code: "mi", Name: "Maori", NativeName: "Māori"}, + {Code: "ne", Name: "Nepali", NativeName: "नेपाली"}, + {Code: "no", Name: "Norwegian", NativeName: "Norsk"}, + {Code: "fa", Name: "Persian", NativeName: "فارسی"}, + {Code: "pl", Name: "Polish", NativeName: "Polski"}, + {Code: "pt", Name: "Portuguese", NativeName: "Português"}, + {Code: "ro", Name: "Romanian", NativeName: "Română"}, + {Code: "ru", Name: "Russian", NativeName: "Русский"}, + {Code: "sr", Name: "Serbian", NativeName: "Српски"}, + {Code: "sk", Name: "Slovak", NativeName: "Slovenčina"}, + {Code: "sl", Name: "Slovenian", NativeName: "Slovenščina"}, + {Code: "es", Name: "Spanish", NativeName: "Español"}, + {Code: "sw", Name: "Swahili", NativeName: "Kiswahili"}, + {Code: "sv", Name: "Swedish", NativeName: "Svenska"}, + {Code: "tl", Name: "Tagalog", NativeName: "Tagalog"}, + {Code: "ta", Name: "Tamil", NativeName: "தமிழ்"}, + {Code: "th", Name: "Thai", NativeName: "ไทย"}, + {Code: "tr", Name: "Turkish", NativeName: "Türkçe"}, + {Code: "uk", Name: "Ukrainian", NativeName: "Українська"}, + {Code: "ur", Name: "Urdu", NativeName: "اردو"}, + {Code: "vi", Name: "Vietnamese", NativeName: "Tiếng Việt"}, + {Code: "cy", Name: "Welsh", NativeName: "Cymraeg"}, +} + +// codeIndex maps language codes to their Language structs for fast lookup +var codeIndex map[string]Language + +func init() { + codeIndex = make(map[string]Language, len(languages)+1) + codeIndex[""] = Auto // auto-detect is valid + for _, lang := range languages { + codeIndex[lang.Code] = lang + } +} + +// FromCode returns the Language for the given code. +// Returns Auto if code is not found. +func FromCode(code string) Language { + if lang, ok := codeIndex[code]; ok { + return lang + } + return Auto +} + +// List returns all supported languages (excluding Auto) +func List() []Language { + result := make([]Language, len(languages)) + copy(result, languages) + return result +} + +// Codes returns all language codes (excluding empty string for auto) +func Codes() []string { + codes := make([]string, len(languages)) + for i, lang := range languages { + codes[i] = lang.Code + } + return codes +} + +// AllLanguageCodes is an alias for Codes - used by models that support all languages +func AllLanguageCodes() []string { + return Codes() +} + +// IsValidCode returns true if the code is recognized (including empty for auto) +func IsValidCode(code string) bool { + _, ok := codeIndex[code] + return ok +} diff --git a/internal/language/language_test.go b/internal/language/language_test.go new file mode 100644 index 0000000..7eda70e --- /dev/null +++ b/internal/language/language_test.go @@ -0,0 +1,119 @@ +package language + +import "testing" + +func TestFromCode(t *testing.T) { + tests := []struct { + code string + wantCode string + wantName string + }{ + {"en", "en", "English"}, + {"es", "es", "Spanish"}, + {"zh", "zh", "Chinese"}, + {"invalid", "", "Auto-detect"}, + {"", "", "Auto-detect"}, + } + + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + got := FromCode(tt.code) + if got.Code != tt.wantCode { + t.Errorf("FromCode(%q).Code = %q, want %q", tt.code, got.Code, tt.wantCode) + } + if got.Name != tt.wantName { + t.Errorf("FromCode(%q).Name = %q, want %q", tt.code, got.Name, tt.wantName) + } + }) + } +} + +func TestFromCodeEnglish(t *testing.T) { + lang := FromCode("en") + if lang.Code != "en" { + t.Errorf("FromCode('en').Code = %q, want 'en'", lang.Code) + } + if lang.Name != "English" { + t.Errorf("FromCode('en').Name = %q, want 'English'", lang.Name) + } + if lang.NativeName != "English" { + t.Errorf("FromCode('en').NativeName = %q, want 'English'", lang.NativeName) + } +} + +func TestIsValidCode(t *testing.T) { + tests := []struct { + code string + want bool + }{ + {"en", true}, + {"es", true}, + {"zh", true}, + {"invalid", false}, + {"", true}, // auto is valid + {"xyz", false}, + } + + for _, tt := range tests { + t.Run(tt.code, func(t *testing.T) { + got := IsValidCode(tt.code) + if got != tt.want { + t.Errorf("IsValidCode(%q) = %v, want %v", tt.code, got, tt.want) + } + }) + } +} + +func TestList(t *testing.T) { + list := List() + if len(list) != 57 { + t.Errorf("List() returned %d languages, want 57", len(list)) + } + + // verify English is in the list + found := false + for _, lang := range list { + if lang.Code == "en" { + found = true + break + } + } + if !found { + t.Error("List() does not contain English") + } +} + +func TestCodes(t *testing.T) { + codes := Codes() + if len(codes) != 57 { + t.Errorf("Codes() returned %d codes, want 57", len(codes)) + } + + // verify 'en' is in the codes + found := false + for _, code := range codes { + if code == "en" { + found = true + break + } + } + if !found { + t.Error("Codes() does not contain 'en'") + } +} + +func TestAllLanguageCodes(t *testing.T) { + codes := AllLanguageCodes() + if len(codes) != 57 { + t.Errorf("AllLanguageCodes() returned %d codes, want 57", len(codes)) + } +} + +func TestAuto(t *testing.T) { + if Auto.Code != "" { + t.Errorf("Auto.Code = %q, want empty string", Auto.Code) + } + if Auto.Name != "Auto-detect" { + t.Errorf("Auto.Name = %q, want 'Auto-detect'", Auto.Name) + } +} diff --git a/progress.txt b/progress.txt new file mode 100644 index 0000000..ddd2bd0 --- /dev/null +++ b/progress.txt @@ -0,0 +1,11 @@ +# Ralph Progress Log +Started: Sun Feb 1 12:22:47 AM CET 2026 +--- + +## Completed + +### Task 1: Create language package with core types and helpers +- Created `internal/language/language.go` with Language struct, Auto constant +- Implemented FromCode, List, Codes, AllLanguageCodes, IsValidCode +- Full 57 language list from OpenAI Whisper +- All tests passing, typecheck passes diff --git a/tasks/prd.jsonc b/tasks/prd.jsonc index 1aea892..81e600a 100644 --- a/tasks/prd.jsonc +++ b/tasks/prd.jsonc @@ -26,7 +26,7 @@ "IsValidCode('') returns true (auto is valid)", "Typecheck passes" ], - "passes": false + "passes": true }, { "title": "Add language list and provider-specific mappings",