package company import ( "context" "fmt" "strings" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) // DefaultLanguage is the content-language fallback when unset. const DefaultLanguage = "en" // ContentLanguages is the allowlist for companies.language (AI/product content). // Keep in sync with apps/web/src/lib/content-languages.ts. var ContentLanguages = []string{ "en", "fr", "de", "es", "it", "nl", "pt", "pl", "cs", "sk", "hu", "ro", "bg", "hr", "sl", "sv", "da", "fi", "el", "et", "lv", "lt", "mt", "ga", "ja", // CJK plug-in slot (Japanese) } // contentLanguageLabels are English display names for AI prompt injection. // Keep in sync with apps/web/src/lib/content-languages.ts labels. var contentLanguageLabels = map[string]string{ "en": "English", "fr": "French", "de": "German", "es": "Spanish", "it": "Italian", "nl": "Dutch", "pt": "Portuguese", "pl": "Polish", "cs": "Czech", "sk": "Slovak", "hu": "Hungarian", "ro": "Romanian", "bg": "Bulgarian", "hr": "Croatian", "sl": "Slovenian", "sv": "Swedish", "da": "Danish", "fi": "Finnish", "el": "Greek", "et": "Estonian", "lv": "Latvian", "lt": "Lithuanian", "mt": "Maltese", "ga": "Irish", "ja": "Japanese", } var contentLanguageSet map[string]struct{} func init() { contentLanguageSet = make(map[string]struct{}, len(ContentLanguages)) for _, code := range ContentLanguages { contentLanguageSet[code] = struct{}{} } } // NormalizeLanguage trims and lowercases a content-language code. func NormalizeLanguage(raw string) string { return strings.ToLower(strings.TrimSpace(raw)) } // IsAllowedLanguage reports whether code is in ContentLanguages (after normalize). func IsAllowedLanguage(raw string) bool { _, ok := contentLanguageSet[NormalizeLanguage(raw)] return ok } // ParseLanguage validates and normalizes a content-language code. // Empty input returns DefaultLanguage when allowEmptyAsDefault is true; // otherwise empty is an error (use for explicit PATCH language fields). func ParseLanguage(raw string, allowEmptyAsDefault bool) (string, error) { code := NormalizeLanguage(raw) if code == "" { if allowEmptyAsDefault { return DefaultLanguage, nil } return "", fmt.Errorf("language is required") } if !IsAllowedLanguage(code) { return "", fmt.Errorf("unsupported language %q", code) } return code, nil } // LanguageLabel returns the English display name for a content-language code // (for AI prompt injection). Empty/unknown codes fall back to English. func LanguageLabel(raw string) string { code, err := ParseLanguage(raw, true) if err != nil { code = DefaultLanguage } if label, ok := contentLanguageLabels[code]; ok { return label } return contentLanguageLabels[DefaultLanguage] } // LoadLanguage returns companies.language for companyID, or DefaultLanguage. func LoadLanguage(ctx context.Context, pool *pgxpool.Pool, companyID uuid.UUID) string { if pool == nil { return DefaultLanguage } var raw string err := pool.QueryRow(ctx, `SELECT COALESCE(language, '') FROM companies WHERE id = $1`, companyID).Scan(&raw) if err != nil { return DefaultLanguage } code, err := ParseLanguage(raw, true) if err != nil { return DefaultLanguage } return code }