// Package i18n holds the languages a blog can be in and the translations of // the fixed text (dashboard, messages, dates). Keys are the English strings // themselves, so an untranslated key renders as English rather than blank. package i18n import ( "fmt" "strings" "time" ) // Default is what new blogs get and what anything unknown falls back to. const Default = "en" type Language struct { Code, Name string // Name is in the language itself, for the settings menu } var languages = []Language{ {Code: "en", Name: "English"}, {Code: "el", Name: "Ελληνικά"}, } // catalogs maps a language to its translations; English is the identity. var catalogs = map[string]map[string]string{ "el": el, } func Languages() []Language { return languages } func Valid(code string) bool { for _, l := range languages { if l.Code == code { return true } } return false } // T translates key into lang, returning the key itself when there is no translation. func T(lang, key string) string { if s, ok := catalogs[lang][key]; ok { return s } return key } // Tf is T followed by Sprintf; translations may reorder arguments with %[n]s. func Tf(lang, key string, args ...any) string { return fmt.Sprintf(T(lang, key), args...) } // Keys lists what a catalog translates (for the completeness test). func Keys(lang string) []string { out := make([]string, 0, len(catalogs[lang])) for k := range catalogs[lang] { out = append(out, k) } return out } // Match picks a supported language from an Accept-Language header: the first // tag (in header order) whose primary subtag we have, else Default. func Match(acceptLanguage string) string { for _, part := range strings.Split(acceptLanguage, ",") { tag, _, _ := strings.Cut(strings.TrimSpace(part), ";") primary, _, _ := strings.Cut(strings.ToLower(tag), "-") if Valid(primary) { return primary } } return Default } // months are the names a language needs: standalone (archive headings), in a // date ("14 September" — Greek uses the genitive there) and abbreviated. type months struct { name, inDate, short [12]string } var monthTables = map[string]months{ "el": elMonths, } // Month is the standalone month name (archive headings). func Month(lang string, m time.Month) string { if t, ok := monthTables[lang]; ok { return t.name[m-1] } return m.String() } // FormatDate renders a post date in the blogger's chosen style (long | short | iso). func FormatDate(lang, style string, at time.Time) string { if style == "iso" { return at.Format("2006-01-02") } t, ok := monthTables[lang] if !ok { // English: the Go layouts if style == "short" { return at.Format("2 Jan 2006") } return at.Format("2 January 2006") } m := t.inDate[at.Month()-1] if style == "short" { m = t.short[at.Month()-1] } return fmt.Sprintf("%d %s %d", at.Day(), m, at.Year()) }