cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / sheet / locale.go
locale.go 6.3K
1package sheet23import (4 "strings"5 "sync/atomic"67 "src.rest/cat/012/internal/formula"8 "src.rest/cat/012/internal/locale"9 "src.rest/cat/012/internal/numfmt"10 "src.rest/cat/012/internal/value"11)1213// A workbook's locale is how its entries are typed and its numbers,14// dates and formulas shown, as Sheets' File > Settings > Locale. Cells15// keep their entries in en-US's form whatever the locale (see16// value.Canonicalize and formula.Delocalize), so values never depend on17// it: switching the locale redraws the sheet and changes nothing else,18// and a file reads the same everywhere. Functions compute in en-US's19// form too (VALUE("1,5") is an error in every locale), so formulas give20// the same results everywhere.2122// settings are the workbook's own settings, saved in its file: Sheets'23// File > Settings.24type settings struct {25 decimal bool // decimal arithmetic, see decimal.go26 locale *locale.Locale // nil follows the default27}2829// defaultLocale is the locale of workbooks that don't name one: the30// config's locale, set with SetDefaultLocale.31var defaultLocale atomic.Pointer[locale.Locale]3233// SetDefaultLocale sets the locale of workbooks that don't name one of34// their own; nil is en-US.35func SetDefaultLocale(l *locale.Locale) { defaultLocale.Store(l) }3637// DefaultLocale is the locale of workbooks that don't name one.38func DefaultLocale() *locale.Locale { return defaultLocale.Load().Or() }3940// Locale is the workbook's locale: its own, or the default.41func (w *Workbook) Locale() *locale.Locale {42 if w.locale != nil {43 return w.locale44 }45 return DefaultLocale()46}4748// LocaleTag is the tag of the locale the workbook names, or "" when it49// follows the default.50func (w *Workbook) LocaleTag() string {51 if w.locale == nil {52 return ""53 }54 return w.locale.Tag55}5657// SetLocale sets the workbook's locale by its tag, "" to follow the58// default, as one undo step. Values don't change, only how they're59// typed and shown. It reports false for a tag it doesn't know.60func (w *Workbook) SetLocale(tag string) bool {61 var l *locale.Locale62 if tag != "" {63 var ok bool64 if l, ok = locale.Lookup(tag); !ok {65 return false66 }67 }68 if l == w.locale {69 return true70 }71 label := "follow the default locale"72 if l != nil {73 label = "set the locale to " + l.Tag74 }75 w.change(w.sheets[w.Active()], label, Rect{}, func() {76 w.recordSettings()77 w.locale = l78 })79 return true80}8182// Locale is the sheet's workbook's.83func (s *Sheet) Locale() *locale.Locale { return s.wb.Locale() }8485// CanonicalEntry is an entry typed in loc as the cell stores it: numbers,86// dates and times in en-US's form, formulas with its separators, text as87// typed, behind a ' when it would read as something else stored (1.5,88// typed as text in fr-FR).89func CanonicalEntry(typed string, loc *locale.Locale) string {90 if loc.IsCanonical() || typed == "" || strings.HasPrefix(typed, "'") {91 return typed92 }93 if c, ok := value.Canonicalize(typed, loc); ok {94 return c95 }96 switch strings.ToUpper(typed) {97 case "TRUE", "FALSE":98 return typed99 }100 if strings.HasPrefix(typed, "=") {101 return formula.Delocalize(typed, loc)102 }103 if strings.HasPrefix(typed, "+") || strings.HasPrefix(typed, "-") {104 if f := formula.Delocalize(typed, loc); IsFormulaEntry(f) {105 return f106 }107 }108 if storedAsOther(typed) {109 return "'" + typed110 }111 return typed112}113114// LocalEntry is the inverse of CanonicalEntry: a cell's entry as typed115// in loc, as the formula bar shows it and editing starts from.116func LocalEntry(input string, loc *locale.Locale) string {117 if loc.IsCanonical() || input == "" {118 return input119 }120 if rest, ok := strings.CutPrefix(input, "'"); ok {121 // The ' CanonicalEntry adds, when typing rest in loc is text.122 if storedAsOther(rest) && CanonicalEntry(rest, loc) == input {123 return rest124 }125 return input126 }127 if _, _, ok := ParseValue(input); ok {128 return value.Localize(input, loc)129 }130 if IsFormulaEntry(input) {131 return formula.Localize(input, loc)132 }133 return input134}135136// CanonicalArg is the argument of a rule or filter condition typed in137// loc as it's stored: a number or date in en-US's form, a formula (after138// =) in its syntax, text as typed.139func CanonicalArg(arg string, loc *locale.Locale) string {140 if loc.IsCanonical() {141 return arg142 }143 if c, ok := value.Canonicalize(arg, loc); ok {144 return c145 }146 if strings.HasPrefix(arg, "=") {147 return formula.Delocalize(arg, loc)148 }149 return arg150}151152// LocalArg is the inverse of CanonicalArg, for editing an argument again.153func LocalArg(arg string, loc *locale.Locale) string {154 if loc.IsCanonical() {155 return arg156 }157 if _, _, ok := ParseValue(arg); ok {158 return value.Localize(arg, loc)159 }160 if strings.HasPrefix(arg, "=") {161 return formula.Localize(arg, loc)162 }163 return arg164}165166// LocalizeError writes a formula's parse error, alone or wrapped, as the167// formula was typed in loc: "Expected ; or ) in ROUND" in de-DE.168func LocalizeError(err error, loc *locale.Locale) error { return formula.LocalizeError(err, loc) }169170// LocalText is the cell's value as its format displays it in the171// sheet's locale, with no width limit: what text conditions test.172func (s *Sheet) LocalText(a Addr) string {173 if !s.cells.filledAt(a) {174 return ""175 }176 return FormatTextIn(s.cells.value(a), s.DisplayFormat(a), s.Locale())177}178179// localLabel is the cell at a, shown as shown in en-US, as its180// locale shows it.181func (s *Sheet) localLabel(a Addr, shown string) string {182 if shown == "" || s.Locale().IsCanonical() {183 return shown184 }185 return s.LocalText(a)186}187188// CondArg is a condition's value typed in loc as it's kept: as typed189// for a condition on text, as CanonicalArg for the rest.190func CondArg(op CondOp, typed string, loc *locale.Locale) string {191 if op.OnText() {192 return typed193 }194 return CanonicalArg(typed, loc)195}196197// LocalCondArg is the inverse of CondArg, for editing a value again.198func LocalCondArg(op CondOp, arg string, loc *locale.Locale) string {199 if op.OnText() {200 return arg201 }202 return LocalArg(arg, loc)203}204205// textIn is v as General shows it in loc: 1,5 in de-DE.206func textIn(v Value, loc *locale.Locale) string {207 if v.Kind == Number {208 return numfmt.GeneralIn(v.Num, loc)209 }210 return v.String()211}212213// storedAsOther reports whether an entry stored as s isn't text: a214// number, date, formula or boolean.215func storedAsOther(s string) bool {216 if _, _, ok := ParseValue(s); ok {217 return true218 }219 switch strings.ToUpper(s) {220 case "TRUE", "FALSE":221 return true222 }223 return IsFormulaEntry(s)224}