1package ui23import (4 "strings"56 tea "charm.land/bubbletea/v2"78 "src.rest/cat/012/internal/formula"9 "src.rest/cat/012/internal/locale"10 "src.rest/cat/012/internal/numfmt"11 "src.rest/cat/012/internal/sheet"12 "src.rest/cat/012/internal/ui/picker"13)1415// File > Settings > Locale: how the workbook's entries are typed and its16// numbers, dates and formulas shown (see sheet/locale.go). Cells keep17// their entries in en-US's form, so the UI translates at its edges: what18// the formula bar and editing show (shownEntry), and what's typed or19// pasted before it's stored (storedEntry).2021func init() {22 register(&command{id: "settings.locale", title: "Locale",23 desc: "Type and show numbers, dates, currency and formulas as a country does: 1.234,56 and =ROUND(A1; 2) in Germany",24 run: func(m *Model) tea.Cmd { m.openLocalePicker(); return nil }})25}2627// locale is the workbook's locale.28func (m *Model) locale() *locale.Locale { return m.sheet.Locale() }2930// shownEntry is a cell's entry as typed in the workbook's locale.31func (m *Model) shownEntry(input string) string { return sheet.LocalEntry(input, m.locale()) }3233// storedEntry is text typed in the workbook's locale as the cell stores34// it.35func (m *Model) storedEntry(typed string) string { return sheet.CanonicalEntry(typed, m.locale()) }3637// storedFormula is a formula being typed, in the syntax it's parsed in:38// what the caret's function and argument are read from. It has the same39// runes at the same places.40func (m *Model) storedFormula(buf []rune) []rune {41 if formula.SameSyntax(m.locale()) {42 return buf43 }44 return []rune(formula.Delocalize(string(buf), m.locale()))45}4647// canPoint reports whether the caret follows an operator a reference may48// come after; not a decimal comma.49func (m *Model) canPoint() bool {50 if !m.line.CanPoint() {51 return false52 }53 return m.line.Buf[m.line.Pos-1] != ',' || m.locale().Decimal != ','54}5556// openLocalePicker lists the locales, the workbook's own highlighted;57// the first follows the config's locale.58func (m *Model) openLocalePicker() {59 cur := m.book().LocaleTag()60 def := sheet.DefaultLocale()61 items := []picker.Item{m.localeItem("Default", "", def)}62 items[0].Desc = "Follow the locale setting, " + def.Name + ". " + localeDesc(def)63 sel := 064 for i, l := range locale.All() {65 if l.Tag == cur {66 sel = i + 167 }68 items = append(items, m.localeItem(l.Name, l.Tag, l))69 }70 p := m.newPicker("Locale", "Type a language, country or tag", 80, items)71 p.Action = "set"72 p.Answers = true73 p.Sel = sel74 p.Narrow = narrowTags75 m.openOverlay(p)76}7778// narrowTags keeps the locales whose tag starts with the search, when79// any does: "de" lists German ones rather than Denmark first.80func narrowTags(query string) (string, func(*picker.Item) bool) {81 q := strings.ToLower(strings.TrimSpace(query))82 if len(q) < 2 {83 return query, nil84 }85 tagged := func(it *picker.Item) bool { return strings.HasPrefix(strings.ToLower(it.Detail), q) }86 for _, tag := range locale.Tags() {87 if strings.HasPrefix(strings.ToLower(tag), q) {88 return "", tagged89 }90 }91 return query, nil92}9394// localeItem is the picker's row for l, which picking sets as tag.95func (m *Model) localeItem(title, tag string, l *locale.Locale) picker.Item {96 return picker.Item{97 Title: title, Name: len(title), Detail: l.Tag + " " + localeSample(l), Desc: localeDesc(l),98 Pick: func() tea.Cmd {99 m.closeOverlay()100 m.setLocale(tag)101 return nil102 },103 }104}105106// localeSample shows a number, a date and a formula in l.107func localeSample(l *locale.Locale) string {108 return numfmt.FormatIn(1234.56, "#,##0.00", l) + " " + numfmt.FormatIn(sampleDate, l.Date, l) + " " + sampleFormula(l)109}110111// localeDesc says how l writes currency, dates and formulas.112func localeDesc(l *locale.Locale) string {113 return "Numbers " + numfmt.FormatIn(1234.56, sheet.Preset(sheet.FmtCurrency).CodeIn(l), l) +114 ", dates " + numfmt.FormatIn(sampleDate, l.Date, l) + ", formulas " + sampleFormula(l)115}116117// sampleFormula is a formula with a decimal and two arguments, in l.118func sampleFormula(l *locale.Locale) string { return formula.Localize("=ROUND(1.5, 1)", l) }119120// sampleDate is the date locales are shown with.121var sampleDate = numfmt.DateSerial(2026, 9, 26)122123// setLocale sets the workbook's locale, "" to follow the default.124func (m *Model) setLocale(tag string) {125 m.book().SetLocale(tag)126 m.changed = m.sheet.StateID() != m.saved127 l := m.locale()128 name := l.Name129 if tag == "" {130 name = "the default, " + l.Name131 }132 m.note = "Locale " + name + ". " + localeDesc(l)133}