94 lines
2.2 KiB
Go
94 lines
2.2 KiB
Go
// Package hu holds the Hungarian-locale formatting the server needs for share
|
||
// metadata and share images. The browser keeps using Intl for everything it
|
||
// renders itself; this exists because og: tags and PNGs are produced here.
|
||
package hu
|
||
|
||
import (
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
"unicode"
|
||
)
|
||
|
||
// groupSeparator is U+00A0, which is what Intl.NumberFormat("hu-HU") emits.
|
||
// A plain space here would make the share card disagree with the page.
|
||
const groupSeparator = " "
|
||
|
||
func FormatNumber(n int) string {
|
||
s := strconv.Itoa(n)
|
||
neg := strings.HasPrefix(s, "-")
|
||
s = strings.TrimPrefix(s, "-")
|
||
|
||
var b strings.Builder
|
||
for i, r := range s {
|
||
if i > 0 && (len(s)-i)%3 == 0 {
|
||
b.WriteString(groupSeparator)
|
||
}
|
||
b.WriteRune(r)
|
||
}
|
||
if neg {
|
||
return "-" + b.String()
|
||
}
|
||
return b.String()
|
||
}
|
||
|
||
// FormatHUF renders 1351600 as "1 351 600 Ft".
|
||
func FormatHUF(n int) string { return FormatNumber(n) + " Ft" }
|
||
|
||
func ProgressPct(raised, goal int) int {
|
||
if goal <= 0 {
|
||
return 0
|
||
}
|
||
return int(float64(raised)/float64(goal)*100 + 0.5)
|
||
}
|
||
|
||
// DaysLeft counts whole days until the end of the closing day in Budapest —
|
||
// SPEC.md section 6. Never negative: a finished campaign has zero days left,
|
||
// not a negative countdown.
|
||
func DaysLeft(endsAt string, loc *time.Location, now time.Time) int {
|
||
d, err := time.ParseInLocation("2006-01-02", endsAt, loc)
|
||
if err != nil {
|
||
return 0
|
||
}
|
||
end := d.Add(24*time.Hour - time.Second)
|
||
diff := end.Sub(now)
|
||
if diff <= 0 {
|
||
return 0
|
||
}
|
||
days := int(diff / (24 * time.Hour))
|
||
if diff%(24*time.Hour) > 0 {
|
||
days++
|
||
}
|
||
return days
|
||
}
|
||
|
||
var accentFolds = map[rune]rune{
|
||
'á': 'a', 'é': 'e', 'í': 'i', 'ó': 'o', 'ö': 'o', 'ő': 'o',
|
||
'ú': 'u', 'ü': 'u', 'ű': 'u',
|
||
}
|
||
|
||
// Slugify mirrors the browser-side helper so a slug the editor previews is the
|
||
// slug that gets stored.
|
||
func Slugify(input string) string {
|
||
var b strings.Builder
|
||
for _, r := range strings.ToLower(input) {
|
||
if folded, ok := accentFolds[r]; ok {
|
||
r = folded
|
||
}
|
||
if (r >= 'a' && r <= 'z') || unicode.IsDigit(r) {
|
||
b.WriteRune(r)
|
||
} else {
|
||
b.WriteRune('-')
|
||
}
|
||
}
|
||
slug := b.String()
|
||
for strings.Contains(slug, "--") {
|
||
slug = strings.ReplaceAll(slug, "--", "-")
|
||
}
|
||
slug = strings.Trim(slug, "-")
|
||
if len(slug) > 80 {
|
||
slug = strings.Trim(slug[:80], "-")
|
||
}
|
||
return slug
|
||
}
|