Add currency_pattern to language relation

The design calls for rendering all amounts with their currency symbol,
but golang.org/x/text’s currency package always render the symbol in
front, which is wrong in Catalan and Spanish, and a lot of other
languages.

Consulting the Internet, the most popular package for that is
accounting[0], which is almost as useless because they confuse locale
with the currency’s country of origin’s “usual locale” (e.g., en-US for
USD), which is also wrong: in Catalan i need to write USD prices as
"1.234,56 $" regardless of what Americans do.

With accounting i have the recourse of initializing the struct that
holds all the “locale” information, which is also wrong because i have
to define the decimal and thousands separators, something that depends
only on the locale, next to the currency’s precision, that is
locale-independent.  But, since all CLDR data from golang.org/x/text
is inside an internal package, i can not access it and would need to
define all that information myself, which defeats the purpose of using
an external package.

Since for now i only need the format pattern for currency, i just saved
it into the database of available languages, that i do not expect to
grow too much.

[0]: https://github.com/leekchan/accounting
This commit is contained in:
jordi fita mas 2023-02-23 12:12:33 +01:00
parent 97ef02b0f9
commit 8dbf8ef2d0
9 changed files with 43 additions and 23 deletions

View File

@ -4,10 +4,10 @@
begin;
insert into public.language (lang_tag, name, endonym, selectable)
values ('und', 'Undefined', 'Undefined', false)
, ('ca', 'Catalan', 'català', true)
, ('es', 'Spanish', 'español', true)
insert into public.language (lang_tag, name, endonym, selectable, currency_pattern)
values ('und', 'Undefined', 'Undefined', false, '%[3]s%.[1]*[2]f')
, ('ca', 'Catalan', 'català', true, '%.[1]*[2]f %[3]s')
, ('es', 'Spanish', 'español', true, '%.[1]*[2]f %[3]s')
;
commit;

View File

@ -9,7 +9,8 @@ create table language (
lang_tag text primary key check (length(lang_tag) < 36), -- RFC5646 recommends 35 at least
name text not null,
endonym text not null,
selectable boolean not null
selectable boolean not null,
currency_pattern text not null
);
grant select on table language to guest;

View File

@ -12,27 +12,31 @@ const contextLocaleKey = "numerus-locale"
type Locale struct {
*gotext.Locale
Language language.Tag
CurrencyPattern string
Language language.Tag
}
func NewLocale(lang language.Tag) *Locale {
func NewLocale(lang availableLanguage) *Locale {
return &Locale{
gotext.NewLocale("locales", lang.String()),
lang,
gotext.NewLocale("locales", lang.tag.String()),
lang.currencyPattern,
lang.tag,
}
}
func LocaleSetter(db *Db, next http.Handler) http.Handler {
availableLanguages := mustGetAvailableLanguages(db)
var matcher = language.NewMatcher(availableLanguages)
locales := map[language.Tag]*Locale{}
var tags []language.Tag
for _, lang := range availableLanguages {
locale := NewLocale(lang)
locale.AddDomain("numerus")
locales[lang] = locale
locales[lang.tag] = locale
tags = append(tags, lang.tag)
}
defaultLocale := locales[language.Catalan]
var matcher = language.NewMatcher(tags)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var locale *Locale
@ -70,21 +74,27 @@ func gettext(str string, locale *Locale) string {
return locale.Get(str)
}
func mustGetAvailableLanguages(db *Db) []language.Tag {
rows, err := db.Query(context.Background(), "select lang_tag from language where selectable")
type availableLanguage struct {
tag language.Tag
currencyPattern string
}
func mustGetAvailableLanguages(db *Db) []availableLanguage {
rows, err := db.Query(context.Background(), "select lang_tag, currency_pattern from language where selectable")
if err != nil {
panic(err)
}
defer rows.Close()
var langs []language.Tag
var langs []availableLanguage
for rows.Next() {
var langTag string
err = rows.Scan(&langTag)
var currencyPattern string
err = rows.Scan(&langTag, &currencyPattern)
if err != nil {
panic(err)
}
langs = append(langs, language.MustParse(langTag))
langs = append(langs, availableLanguage{language.MustParse(langTag), currencyPattern})
}
if rows.Err() != nil {
panic(rows.Err())

View File

@ -38,7 +38,7 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
if err != nil {
f = math.NaN()
}
return p.Sprintf("%.*f", company.DecimalDigits, number.Decimal(f))
return p.Sprintf(locale.CurrencyPattern, company.DecimalDigits, number.Decimal(f), company.CurrencySymbol)
},
"formatDate": func(time time.Time) string {
return time.Format("02/01/2006")

View File

@ -7,7 +7,7 @@ begin;
set search_path to public;
select plan(23);
select plan(27);
select has_table('language');
select has_pk('language');
@ -37,6 +37,11 @@ select col_type_is('language', 'selectable', 'boolean');
select col_not_null('language', 'selectable');
select col_hasnt_default('language', 'selectable');
select has_column('language', 'currency_pattern');
select col_type_is('language', 'currency_pattern', 'text');
select col_not_null('language', 'currency_pattern');
select col_hasnt_default('language', 'currency_pattern');
select *
from finish();

View File

@ -9,20 +9,23 @@ from language
where lang_tag = 'und'
and name = 'Undefined'
and endonym = 'Undefined'
and not selectable;
and not selectable
and currency_pattern = '%[3]s%.[1]*[2]f';
select 1 / count(*)
from language
where lang_tag = 'ca'
and name = 'Catalan'
and endonym = 'català'
and selectable;
and selectable
and currency_pattern = '%.[1]*[2]f %[3]s';
select 1 / count(*)
from language
where lang_tag = 'es'
and name = 'Spanish'
and endonym = 'español'
and selectable;
and selectable
and currency_pattern = '%.[1]*[2]f %[3]s';
rollback;

View File

@ -6,6 +6,7 @@ select lang_tag
, name
, endonym
, selectable
, currency_pattern
from public.language
where false;

View File

@ -49,7 +49,7 @@
<tr>
<td><input type="checkbox" name="id" id="new-product-id-{{$key}}" value="{{.Id}}"></td>
<td><label for="new-product-id-{{$key}}">{{ .Name }}</label></td>
<td>{{ .Price | formatPrice }}</td>
<td class="numeric">{{ .Price | formatPrice }}</td>
</tr>
{{- end }}
{{ else }}

View File

@ -29,7 +29,7 @@
<tr>
<td></td>
<td><a href="{{ companyURI "/products/"}}{{ .Slug }}">{{ .Name }}</a></td>
<td>{{ .Price | formatPrice }}</td>
<td class="numeric">{{ .Price | formatPrice }}</td>
</tr>
{{- end }}
{{ else }}