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;
|
begin;
|
||||||
|
|
||||||
insert into public.language (lang_tag, name, endonym, selectable)
|
insert into public.language (lang_tag, name, endonym, selectable, currency_pattern)
|
||||||
values ('und', 'Undefined', 'Undefined', false)
|
values ('und', 'Undefined', 'Undefined', false, '%[3]s%.[1]*[2]f')
|
||||||
, ('ca', 'Catalan', 'català', true)
|
, ('ca', 'Catalan', 'català', true, '%.[1]*[2]f %[3]s')
|
||||||
, ('es', 'Spanish', 'español', true)
|
, ('es', 'Spanish', 'español', true, '%.[1]*[2]f %[3]s')
|
||||||
;
|
;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|
|
@ -9,7 +9,8 @@ create table language (
|
||||||
lang_tag text primary key check (length(lang_tag) < 36), -- RFC5646 recommends 35 at least
|
lang_tag text primary key check (length(lang_tag) < 36), -- RFC5646 recommends 35 at least
|
||||||
name text not null,
|
name text not null,
|
||||||
endonym 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;
|
grant select on table language to guest;
|
||||||
|
|
|
@ -12,27 +12,31 @@ const contextLocaleKey = "numerus-locale"
|
||||||
|
|
||||||
type Locale struct {
|
type Locale struct {
|
||||||
*gotext.Locale
|
*gotext.Locale
|
||||||
|
CurrencyPattern string
|
||||||
Language language.Tag
|
Language language.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocale(lang language.Tag) *Locale {
|
func NewLocale(lang availableLanguage) *Locale {
|
||||||
return &Locale{
|
return &Locale{
|
||||||
gotext.NewLocale("locales", lang.String()),
|
gotext.NewLocale("locales", lang.tag.String()),
|
||||||
lang,
|
lang.currencyPattern,
|
||||||
|
lang.tag,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func LocaleSetter(db *Db, next http.Handler) http.Handler {
|
func LocaleSetter(db *Db, next http.Handler) http.Handler {
|
||||||
availableLanguages := mustGetAvailableLanguages(db)
|
availableLanguages := mustGetAvailableLanguages(db)
|
||||||
var matcher = language.NewMatcher(availableLanguages)
|
|
||||||
|
|
||||||
locales := map[language.Tag]*Locale{}
|
locales := map[language.Tag]*Locale{}
|
||||||
|
var tags []language.Tag
|
||||||
for _, lang := range availableLanguages {
|
for _, lang := range availableLanguages {
|
||||||
locale := NewLocale(lang)
|
locale := NewLocale(lang)
|
||||||
locale.AddDomain("numerus")
|
locale.AddDomain("numerus")
|
||||||
locales[lang] = locale
|
locales[lang.tag] = locale
|
||||||
|
tags = append(tags, lang.tag)
|
||||||
}
|
}
|
||||||
defaultLocale := locales[language.Catalan]
|
defaultLocale := locales[language.Catalan]
|
||||||
|
var matcher = language.NewMatcher(tags)
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var locale *Locale
|
var locale *Locale
|
||||||
|
@ -70,21 +74,27 @@ func gettext(str string, locale *Locale) string {
|
||||||
return locale.Get(str)
|
return locale.Get(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetAvailableLanguages(db *Db) []language.Tag {
|
type availableLanguage struct {
|
||||||
rows, err := db.Query(context.Background(), "select lang_tag from language where selectable")
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var langs []language.Tag
|
var langs []availableLanguage
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var langTag string
|
var langTag string
|
||||||
err = rows.Scan(&langTag)
|
var currencyPattern string
|
||||||
|
err = rows.Scan(&langTag, ¤cyPattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
langs = append(langs, language.MustParse(langTag))
|
langs = append(langs, availableLanguage{language.MustParse(langTag), currencyPattern})
|
||||||
}
|
}
|
||||||
if rows.Err() != nil {
|
if rows.Err() != nil {
|
||||||
panic(rows.Err())
|
panic(rows.Err())
|
||||||
|
|
|
@ -38,7 +38,7 @@ func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f = math.NaN()
|
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 {
|
"formatDate": func(time time.Time) string {
|
||||||
return time.Format("02/01/2006")
|
return time.Format("02/01/2006")
|
||||||
|
|
|
@ -7,7 +7,7 @@ begin;
|
||||||
|
|
||||||
set search_path to public;
|
set search_path to public;
|
||||||
|
|
||||||
select plan(23);
|
select plan(27);
|
||||||
|
|
||||||
select has_table('language');
|
select has_table('language');
|
||||||
select has_pk('language');
|
select has_pk('language');
|
||||||
|
@ -37,6 +37,11 @@ select col_type_is('language', 'selectable', 'boolean');
|
||||||
select col_not_null('language', 'selectable');
|
select col_not_null('language', 'selectable');
|
||||||
select col_hasnt_default('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 *
|
select *
|
||||||
from finish();
|
from finish();
|
||||||
|
|
||||||
|
|
|
@ -9,20 +9,23 @@ from language
|
||||||
where lang_tag = 'und'
|
where lang_tag = 'und'
|
||||||
and name = 'Undefined'
|
and name = 'Undefined'
|
||||||
and endonym = 'Undefined'
|
and endonym = 'Undefined'
|
||||||
and not selectable;
|
and not selectable
|
||||||
|
and currency_pattern = '%[3]s%.[1]*[2]f';
|
||||||
|
|
||||||
select 1 / count(*)
|
select 1 / count(*)
|
||||||
from language
|
from language
|
||||||
where lang_tag = 'ca'
|
where lang_tag = 'ca'
|
||||||
and name = 'Catalan'
|
and name = 'Catalan'
|
||||||
and endonym = 'català'
|
and endonym = 'català'
|
||||||
and selectable;
|
and selectable
|
||||||
|
and currency_pattern = '%.[1]*[2]f %[3]s';
|
||||||
|
|
||||||
select 1 / count(*)
|
select 1 / count(*)
|
||||||
from language
|
from language
|
||||||
where lang_tag = 'es'
|
where lang_tag = 'es'
|
||||||
and name = 'Spanish'
|
and name = 'Spanish'
|
||||||
and endonym = 'español'
|
and endonym = 'español'
|
||||||
and selectable;
|
and selectable
|
||||||
|
and currency_pattern = '%.[1]*[2]f %[3]s';
|
||||||
|
|
||||||
rollback;
|
rollback;
|
||||||
|
|
|
@ -6,6 +6,7 @@ select lang_tag
|
||||||
, name
|
, name
|
||||||
, endonym
|
, endonym
|
||||||
, selectable
|
, selectable
|
||||||
|
, currency_pattern
|
||||||
from public.language
|
from public.language
|
||||||
where false;
|
where false;
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td><input type="checkbox" name="id" id="new-product-id-{{$key}}" value="{{.Id}}"></td>
|
<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><label for="new-product-id-{{$key}}">{{ .Name }}</label></td>
|
||||||
<td>{{ .Price | formatPrice }}</td>
|
<td class="numeric">{{ .Price | formatPrice }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td><a href="{{ companyURI "/products/"}}{{ .Slug }}">{{ .Name }}</a></td>
|
<td><a href="{{ companyURI "/products/"}}{{ .Slug }}">{{ .Name }}</a></td>
|
||||||
<td>{{ .Price | formatPrice }}</td>
|
<td class="numeric">{{ .Price | formatPrice }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
|
Loading…
Reference in New Issue