feat: release v0.26 portable i18n and presentation adapters
This commit is contained in:
120
i18n/types.go
Normal file
120
i18n/types.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// Package i18n provides immutable, instance-scoped message catalogs. It has no
|
||||
// dependency on HTTP, application configuration, business errors or storage.
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const Version = 1
|
||||
|
||||
// Kind describes portable message arguments, independently of Go identifiers.
|
||||
type Kind string
|
||||
|
||||
const (
|
||||
String Kind = "string"
|
||||
Boolean Kind = "boolean"
|
||||
Integer Kind = "integer" // Signed int64; browser callers may use bigint/string.
|
||||
Number Kind = "number" // Finite IEEE-754 float64.
|
||||
)
|
||||
|
||||
// Pattern is a text leaf or a select/plural branch. Branches require "other".
|
||||
// Text uses {name}; {{ and }} escape braces. Literal disables interpolation.
|
||||
// Plural is cardinal, with optional exact integer cases such as "=0".
|
||||
type Pattern struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
Literal bool `json:"literal,omitempty"`
|
||||
Select string `json:"select,omitempty"`
|
||||
Plural string `json:"plural,omitempty"`
|
||||
Cases map[string]Pattern `json:"cases,omitempty"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Params map[string]Kind `json:"params,omitempty"`
|
||||
Value Pattern `json:"value"`
|
||||
}
|
||||
|
||||
func (m *Message) UnmarshalJSON(data []byte) error {
|
||||
type plain Message
|
||||
var raw map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if value, ok := raw["value"]; !ok || string(value) == "null" {
|
||||
return fmt.Errorf("i18n: message requires a value")
|
||||
}
|
||||
for key := range raw {
|
||||
if key != "value" && key != "params" {
|
||||
return fmt.Errorf("i18n: unknown message field")
|
||||
}
|
||||
}
|
||||
var value plain
|
||||
// Pattern's fields are checked by the strict decoder, including nested cases.
|
||||
if err := DecodeJSON(data, &value); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = Message(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Document is the versioned interchange format. Source-locale documents own
|
||||
// parameter schemas; translations inherit them and may repeat identical schemas.
|
||||
type Document struct {
|
||||
Version int `json:"version"`
|
||||
Namespace string `json:"namespace"`
|
||||
Locale string `json:"locale"`
|
||||
Messages map[string]Message `json:"messages"`
|
||||
}
|
||||
|
||||
// Config distinguishes source text from the default selection and enabled locales.
|
||||
// Fallbacks are explicit, ordered edges; cycles are rejected. The default locale
|
||||
// must be enabled. The source locale can remain a fallback-only language.
|
||||
type Config struct {
|
||||
SourceLocale string `json:"sourceLocale"`
|
||||
DefaultLocale string `json:"defaultLocale"`
|
||||
Locales []string `json:"locales"`
|
||||
Fallbacks map[string][]string `json:"fallbacks,omitempty"`
|
||||
}
|
||||
|
||||
type Ref struct{ Namespace, Key string }
|
||||
|
||||
// Result reports the actual dictionary language, which may differ from Requested.
|
||||
type Result struct {
|
||||
Text string
|
||||
Requested string
|
||||
Locale string
|
||||
Fallback bool
|
||||
}
|
||||
|
||||
// Diagnostic deliberately excludes argument values and message contents.
|
||||
type Diagnostic struct {
|
||||
Kind string `json:"kind"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Locale string `json:"locale,omitempty"`
|
||||
Param string `json:"param,omitempty"`
|
||||
Problem string `json:"problem"`
|
||||
}
|
||||
|
||||
func (d *Diagnostic) Error() string {
|
||||
return fmt.Sprintf("i18n: %s namespace=%q key=%q locale=%q param=%q: %s", d.Kind, d.Namespace, d.Key, d.Locale, d.Param, d.Problem)
|
||||
}
|
||||
|
||||
type localeKey struct{}
|
||||
|
||||
// WithLocale records a preference; rendering still matches it against the bundle.
|
||||
func WithLocale(ctx context.Context, locale string) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, localeKey{}, locale)
|
||||
}
|
||||
func Locale(ctx context.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
value, _ := ctx.Value(localeKey{}).(string)
|
||||
return value
|
||||
}
|
||||
Reference in New Issue
Block a user