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:
parent
97ef02b0f9
commit
8dbf8ef2d0
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -12,27 +12,31 @@ const contextLocaleKey = "numerus-locale"
|
|||
|
||||
type Locale struct {
|
||||
*gotext.Locale
|
||||
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, ¤cyPattern)
|
||||
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())
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -6,6 +6,7 @@ select lang_tag
|
|||
, name
|
||||
, endonym
|
||||
, selectable
|
||||
, currency_pattern
|
||||
from public.language
|
||||
where false;
|
||||
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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 }}
|
||||
|
|
Loading…
Reference in New Issue