Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preliminary i18n support #13

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions bigbytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,25 +112,39 @@ func humanateBigBytes(s, base *big.Int, sizes []string) string {
// BigBytes produces a human readable representation of an SI size.
//
// BigBytes(82854982) -> 83MB
func BigBytes(s *big.Int) string {
func (h *BaseHumanizer) BigBytes(s *big.Int) string {
sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
return humanateBigBytes(s, bigSIExp, sizes)
}

// BigBytes produces a human readable representation of an SI size.
//
// BigBytes(82854982) -> 83MB
func BigBytes(s *big.Int) string {
return Default.BigBytes(s)
}

// BigIBytes produces a human readable representation of an IEC size.
//
// BigIBytes(82854982) -> 79MiB
func BigIBytes(s *big.Int) string {
func (h *BaseHumanizer) BigIBytes(s *big.Int) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
return humanateBigBytes(s, bigIECExp, sizes)
}

// BigIBytes produces a human readable representation of an IEC size.
//
// BigIBytes(82854982) -> 79MiB
func BigIBytes(s *big.Int) string {
return Default.BigIBytes(s)
}

// ParseBigBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// ParseBigBytes("42MB") -> 42000000, nil
// ParseBigBytes("42mib") -> 44040192, nil
func ParseBigBytes(s string) (*big.Int, error) {
func (h *BaseHumanizer) ParseBigBytes(s string) (*big.Int, error) {
lastDigit := 0
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.') {
Expand All @@ -156,3 +170,12 @@ func ParseBigBytes(s string) (*big.Int, error) {

return nil, fmt.Errorf("unhandled size name: %v", extra)
}

// ParseBigBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// ParseBigBytes("42MB") -> 42000000, nil
// ParseBigBytes("42mib") -> 44040192, nil
func ParseBigBytes(s string) (*big.Int, error) {
return Default.ParseBigBytes(s)
}
40 changes: 32 additions & 8 deletions comma.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ import (
"strings"
)

// Comma produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Comma(834142) -> 834,142
func Comma(v int64) string {
func (h *BaseHumanizer) commaWithSeparator(v int64, separator string) string {
sign := ""
if v < 0 {
sign = "-"
Expand All @@ -32,12 +28,28 @@ func Comma(v int64) string {
j--
}
parts[j] = strconv.Itoa(int(v))
return sign + strings.Join(parts[j:len(parts)], ",")
return sign + strings.Join(parts[j:len(parts)], separator)
}

// Comma produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Comma(834142) -> 834,142
func (h *EnglishHumanizer) Comma(v int64) string {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, the choice of a comma is English-centric. The more general concept is the "decimal mark" or "decimal separator". Many locales use a period instead. From there it gets even weirder.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, not breaking compatibility is critical. But we could add i18n-friendly func aliases :

func (h *EnglishHumanizer) Comma(v int64) string {
  return h.DecimalSeparator(v)
}
func (h *EnglishHumanizer) DecimalSeparator(v int64) string {

return h.commaWithSeparator(v, ",")
}

// Comma produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Comma(834142) -> 834,142
func Comma(v int64) string {
return Default.Comma(v)
}

// BigComma produces a string form of the given big.Int in base 10
// with commas after every three orders of magnitude.
func BigComma(b *big.Int) string {
func (h *BaseHumanizer) bigCommaWithSeparator(b *big.Int, separator string) string {
sign := ""
if b.Sign() < 0 {
sign = "-"
Expand All @@ -63,5 +75,17 @@ func BigComma(b *big.Int) string {
j--
}
parts[j] = strconv.Itoa(int(b.Int64()))
return sign + strings.Join(parts[j:len(parts)], ",")
return sign + strings.Join(parts[j:len(parts)], separator)
}

// BigComma produces a string form of the given big.Int in base 10
// with commas after every three orders of magnitude.
func (h *EnglishHumanizer) BigComma(b *big.Int) string {
return h.bigCommaWithSeparator(b, ",")
}

// BigComma produces a string form of the given big.Int in base 10
// with commas after every three orders of magnitude.
func BigComma(b *big.Int) string {
return Default.BigComma(b)
}
8 changes: 8 additions & 0 deletions english.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package humanize

type EnglishHumanizer struct {
BaseHumanizer
}

var English EnglishHumanizer
var Default = English
7 changes: 6 additions & 1 deletion ftoa.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ func stripTrailingZeros(s string) string {
}

// Ftoa converts a float to a string with no trailing zeros.
func Ftoa(num float64) string {
func (h *BaseHumanizer) Ftoa(num float64) string {
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
}

// Ftoa converts a float to a string with no trailing zeros.
func Ftoa(num float64) string {
return Default.Ftoa(num)
}
2 changes: 2 additions & 0 deletions humanize.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ representing sizes like 82854982 into useful strings like, "83MB" or
"79MiB" (whichever you prefer).
*/
package humanize

type BaseHumanizer struct{}
9 changes: 8 additions & 1 deletion ordinals.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "strconv"
// Ordinal gives you the input number in a rank/ordinal format.
//
// Ordinal(3) -> 3rd
func Ordinal(x int) string {
func (h *EnglishHumanizer) Ordinal(x int) string {
suffix := "th"
switch x % 10 {
case 1:
Expand All @@ -23,3 +23,10 @@ func Ordinal(x int) string {
}
return strconv.Itoa(x) + suffix
}

// Ordinal gives you the input number in a rank/ordinal format.
//
// Ordinal(3) -> 3rd
func Ordinal(x int) string {
return Default.Ordinal(x)
}
32 changes: 29 additions & 3 deletions si.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func init() {
// that prefix.
//
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
func ComputeSI(input float64) (float64, string) {
func (h *BaseHumanizer) ComputeSI(input float64) (float64, string) {
if input == 0 {
return 0, ""
}
Expand All @@ -75,23 +75,42 @@ func ComputeSI(input float64) (float64, string) {
return value, prefix
}

// ComputeSI finds the most appropriate SI prefix for the given number
// and returns the prefix along with the value adjusted to be within
// that prefix.
//
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
func ComputeSI(input float64) (float64, string) {
return Default.ComputeSI(input)
}

// SI returns a string with default formatting.
//
// SI uses Ftoa to format float value, removing trailing zeros.
//
// e.g. SI(1000000, B) -> 1MB
// e.g. SI(2.2345e-12, "F") -> 2.2345pF
func SI(input float64, unit string) string {
func (h *BaseHumanizer) SI(input float64, unit string) string {
value, prefix := ComputeSI(input)
return Ftoa(value) + prefix + unit
}

// SI returns a string with default formatting.
//
// SI uses Ftoa to format float value, removing trailing zeros.
//
// e.g. SI(1000000, B) -> 1MB
// e.g. SI(2.2345e-12, "F") -> 2.2345pF
func SI(input float64, unit string) string {
return Default.SI(input, unit)
}

var errInvalid = errors.New("invalid input")

// ParseSI parses an SI string back into the number and unit.
//
// e.g. ParseSI(2.2345pF) -> (2.2345e-12, "F", nil)
func ParseSI(input string) (float64, string, error) {
func (h *BaseHumanizer) ParseSI(input string) (float64, string, error) {
found := riParseRegex.FindStringSubmatch(input)
if len(found) != 4 {
return 0, "", errInvalid
Expand All @@ -102,3 +121,10 @@ func ParseSI(input string) (float64, string, error) {
base, err := strconv.ParseFloat(found[1], 64)
return base * mag, unit, err
}

// ParseSI parses an SI string back into the number and unit.
//
// e.g. ParseSI(2.2345pF) -> (2.2345e-12, "F", nil)
func ParseSI(input string) (float64, string, error) {
return Default.ParseSI(input)
}
22 changes: 20 additions & 2 deletions times.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ const (
// Time formats a time into a relative string.
//
// Time(someT) -> "3 weeks ago"
func Time(then time.Time) string {
func (h *EnglishHumanizer) Time(then time.Time) string {
return RelTime(then, time.Now(), "ago", "from now")
}

// Time formats a time into a relative string.
//
// Time(someT) -> "3 weeks ago"
func Time(then time.Time) string {
return Default.Time(then)
}

var magnitudes = []struct {
d int64
format string
Expand Down Expand Up @@ -56,7 +63,7 @@ var magnitudes = []struct {
// the label corresponding to the smaller time is applied.
//
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
func RelTime(a, b time.Time, albl, blbl string) string {
func (h *EnglishHumanizer) RelTime(a, b time.Time, albl, blbl string) string {
lbl := albl
diff := b.Unix() - a.Unix()

Expand Down Expand Up @@ -89,3 +96,14 @@ func RelTime(a, b time.Time, albl, blbl string) string {
}
return fmt.Sprintf(mag.format, args...)
}

// RelTime formats a time into a relative string.
//
// It takes two times and two labels. In addition to the generic time
// delta string (e.g. 5 minutes), the labels are used applied so that
// the label corresponding to the smaller time is applied.
//
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
func RelTime(a, b time.Time, albl, blbl string) string {
return Default.RelTime(a, b, albl, blbl)
}