feat: release v0.26 portable i18n and presentation adapters
This commit is contained in:
123
i18n/export.go
Normal file
123
i18n/export.go
Normal file
@@ -0,0 +1,123 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user