124 lines
3.8 KiB
Go
124 lines
3.8 KiB
Go
package i18n
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
// Pack is a self-contained browser artifact for one selected language. Its
|
|
// documents include the entire configured fallback chain and parameter schemas.
|
|
type Pack struct {
|
|
Version int `json:"version"`
|
|
Locale string `json:"locale"`
|
|
Config Config `json:"config"`
|
|
Documents []Document `json:"documents"`
|
|
RuleVersion string `json:"ruleVersion"`
|
|
PluralRules map[string]map[string]string `json:"pluralRules"`
|
|
License string `json:"license"`
|
|
}
|
|
|
|
func (b *Bundle) namespaces(names []string) error {
|
|
if b == nil {
|
|
return fmt.Errorf("i18n: bundle required")
|
|
}
|
|
if len(names) == 0 {
|
|
return fmt.Errorf("i18n: export requires explicit namespaces")
|
|
}
|
|
known := map[string]bool{}
|
|
for ref := range b.entries {
|
|
known[ref.Namespace] = true
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, ns := range names {
|
|
if !known[ns] || seen[ns] {
|
|
return fmt.Errorf("i18n: export namespace is unknown or duplicated: %s", ns)
|
|
}
|
|
seen[ns] = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *Bundle) Export(w io.Writer, locale string, namespaces ...string) error {
|
|
if w == nil {
|
|
return fmt.Errorf("i18n: writer required")
|
|
}
|
|
if err := b.namespaces(namespaces); err != nil {
|
|
return err
|
|
}
|
|
l, err := canonical(locale)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, ok := b.tags[l]; !ok {
|
|
return fmt.Errorf("i18n: export locale must be enabled")
|
|
}
|
|
docs := []Document{}
|
|
for _, candidate := range b.chain(l) {
|
|
docs = append(docs, b.Documents(candidate, namespaces...)...)
|
|
}
|
|
// Source is always needed by clients even if absent from an explicit chain.
|
|
rules := map[string]map[string]string{}
|
|
for _, locale := range b.chain(l) {
|
|
rules[locale] = rulesFor(language.MustParse(locale)).text
|
|
}
|
|
pack := Pack{Version: Version, Locale: l, Config: b.Config(), Documents: docs, RuleVersion: PluralRulesVersion, PluralRules: rules, License: unicodeLicense}
|
|
var buffer bytes.Buffer
|
|
enc := json.NewEncoder(&buffer)
|
|
enc.SetIndent("", " ")
|
|
if err := enc.Encode(pack); err != nil {
|
|
return err
|
|
}
|
|
if buffer.Len() > 4<<20 {
|
|
return fmt.Errorf("i18n: exported pack exceeds 4 MiB; split namespaces")
|
|
}
|
|
_, err = io.Copy(w, &buffer)
|
|
return err
|
|
}
|
|
|
|
// TypeScript emits nested namespace/key types, so keys containing punctuation
|
|
// never collide through concatenation or Go-specific identifier conversion.
|
|
func (b *Bundle) TypeScript(w io.Writer, namespaces ...string) error {
|
|
if w == nil {
|
|
return fmt.Errorf("i18n: writer required")
|
|
}
|
|
if err := b.namespaces(namespaces); err != nil {
|
|
return err
|
|
}
|
|
var out strings.Builder
|
|
out.WriteString("// Generated by go-omni i18n types. Do not edit.\nexport interface MessageParams {\n")
|
|
for _, doc := range b.Documents(b.config.SourceLocale, namespaces...) {
|
|
fmt.Fprintf(&out, " %s: {\n", strconv.Quote(doc.Namespace))
|
|
for _, key := range sortedKeys(doc.Messages) {
|
|
m := doc.Messages[key]
|
|
if len(m.Params) == 0 {
|
|
fmt.Fprintf(&out, " %s: Record<string, never>;\n", strconv.Quote(key))
|
|
continue
|
|
}
|
|
fmt.Fprintf(&out, " %s: {", strconv.Quote(key))
|
|
for _, name := range sortedKeys(m.Params) {
|
|
typ := "string"
|
|
switch m.Params[name] {
|
|
case Boolean:
|
|
typ = "boolean"
|
|
case Integer:
|
|
typ = "number | bigint | `${bigint}`"
|
|
case Number:
|
|
typ = "number"
|
|
}
|
|
fmt.Fprintf(&out, " %s: %s;", strconv.Quote(name), typ)
|
|
}
|
|
out.WriteString(" };\n")
|
|
}
|
|
out.WriteString(" };\n")
|
|
}
|
|
out.WriteString("}\nexport type Namespace = keyof MessageParams;\nexport type Translate = <N extends Namespace, K extends Extract<keyof MessageParams[N], string>>(namespace: N, key: K, params: Extract<MessageParams[N][K], Record<string, unknown>>) => string;\n")
|
|
_, err := io.WriteString(w, out.String())
|
|
return err
|
|
}
|