// Package compactnumber allows for localized CLDR compact number formatting.
package compactnumber import ( ) // Formatter is a struct containing a method to format an integer based on the specified language and compaction type. type Formatter struct { lang language.Tag compactType CompactType } // FormatterAPI is an interface implemented by Formatter that can be used for mocking purposes type FormatterAPI interface { Format(n int, numOptions ...number.Option) (string, error) } // NewFormatter creates a new formatter based on the specified language and compaction type. func ( string, CompactType) Formatter { return Formatter{ lang: language.Make(), compactType: , } } // Format takes in an integer and options and formats it according to the formatter's locale and compaction settings. // Note: this method truncates numbers and does not support fractions (e.g. 11.5M). // // Documented in CLDR spec: http://www.unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats func ( *Formatter) ( int, ...number.Option) (string, error) { = append(, number.Scale(0)) , := compactFormsByLanguage[.lang.String()] if ! { // Fall back to base language , := .lang.Base() if == language.No { return "", errors.New(fmt.Sprintf("no compact forms or fallback for language %s", .lang.String())) } , = compactFormsByLanguage[.String()] if ! { return "", errors.New(fmt.Sprintf("missing compact forms for language %s and fallback %s", .lang.String(), )) } } := [.compactType] // Apply negative modifier at the end if dealing with negative number := 1 if < 0 { = -1 *= -1 } // To format a number N, the greatest type less than or equal to N is used, with the appropriate plural category. var models.CompactFormRule for , := range { if int64() >= .Type { = } else { break } } // N is divided by the type, after removing the number of zeros in the pattern, less 1. := .shortNum(, ) // Best effort fetching plural form := .pluralForm() , := .PatternsByPluralForm[] if ! { // Attempt to fall back to catch-all "other" pattern if none for current plural form found = .PatternsByPluralForm["other"] } var error , = formatPattern() if != nil { return "", } // If the value is precisely “0”, either explicit or defaulted, then the normal number format pattern for that sort of object is supplied := message.NewPrinter(.lang) if == "0" { return .Sprintf("%v", number.Decimal(*, ...)), nil } return .Sprintf(, number.Decimal(*int64(), ...)), nil } // Divides number to be used in compact display according to logic in CLDR spec: http://www.unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats func ( *Formatter) ( int, models.CompactFormRule) int64 { := .Type for := 0; < .ZeroesInPattern-1; ++ { /= 10 } := int64() if != 0 { /= } return } // Gets the pluralized form of the number, as per CLDR spec: http://cldr.unicode.org/index/cldr-spec/plural-rules // We use gotnospirit/makeplural for this as golang.org/x/text/plural does not expose a suitable PluralForm method. // This is a best effort function since the languages might not match up perfectly between packages. func ( *Formatter) ( interface{}) string { , := .lang.Base() if == language.No { return "other" } , := plural.GetFunc(.String()) if != nil { return "other" } return (, false) } // Process CLDR pattern to a format suitable for use in Printer.Sprintf in golang.org/x/text/message. // Documentation for these special characters can be found in the CLDR spec: http://cldr.unicode.org/translation/number-patterns func formatPattern( string) (string, error) { // Default to 0 (sentinel value for no pattern) if == "" { return "0", nil } // Default to the first form if there's multiple = strings.Split(, ";")[0] // Remove special pattern symbols, as this formatting is already handled by golang.org/x/text/message = strings.Replace(, "'", "", -1) // Replace all 0s with a single %v for number formatting := strings.IndexRune(, '0') if == -1 { return "", errors.New(fmt.Sprintf("invalid pattern (no digit pattern characters): %s", )) } = strings.Replace(, "0", "", -1) := []rune() := "" if < len() { = string([:]) } = fmt.Sprintf("%s%s%s", string([:]), "%v", ) return , nil }