Documentation
¶
Overview ¶
Package fluent is a Go implementation of Project Fluent (https://projectfluent.org), a localization system for natural-sounding translations.
It is a port of the reference JavaScript implementation (@fluent/syntax and @fluent/bundle) with one deliberate change: locale-aware formatting (plural rules, numbers, dates) is exposed through pluggable interfaces instead of a hard dependency on a CLDR library. This keeps the core dependency-free.
Layers ¶
- Package fluent (this package): the runtime — a fast FTL parser (NewResource), a fault-tolerant resolver, and Bundle (one locale).
- Package fluent/syntax: the full AST, recursive-descent parser, and serializer used by tooling and conformance.
- Package fluent/fluentx: CLDR-backed PluralRules, NumberFormatter, and DateTimeFormatter backed by the module's self-contained cldr packages (no external dependencies). Import it to enable real locale formatting.
- Package fluent/langneg: language negotiation (a port of @fluent/langneg).
- Package fluent/localization: a high-level layer that formats messages across an ordered chain of locale bundles with fallback.
Basic use ¶
res, _ := fluent.NewResource("hello = Hello, { $name }!")
b := fluent.NewBundle("en")
b.AddResource(res)
msg, _ := b.GetMessage("hello")
var errs []error
out := b.FormatPatternAny(msg.Value, map[string]any{"name": "World"}, &errs)
The resolver is fault-tolerant: it never panics. Missing references and other problems are appended to the errors slice and rendered as fluent.js-style placeholders (for example {$name}); a best-effort string is always returned.
By default placeables are wrapped in Unicode bidirectional isolation marks (FSI/PDI). Disable this with WithUseIsolating(false).
A Bundle is safe for concurrent use: FormatPattern, HasMessage, GetMessage, and the Add* methods (AddFunction, AddResource, AddResourceOverriding) may run from multiple goroutines at once.
Example ¶
Example shows the minimal flow: parse a resource, add it to a bundle, look up a message, and format its pattern with arguments.
package main
import (
"fmt"
fluent "github.com/hakastein/gofluent"
)
func main() {
res, errs := fluent.NewResource("hello = Hello, { $name }!")
if len(errs) > 0 {
panic(errs[0])
}
// useIsolating is disabled here so the output is plain ASCII; in production
// the default (true) wraps placeables in Unicode bidi isolation marks.
b := fluent.NewBundle("en", fluent.WithUseIsolating(false))
b.AddResource(res)
msg, ok := b.GetMessage("hello")
if !ok {
panic("message not found")
}
var ferrs []error
out := b.FormatPatternAny(msg.Value, map[string]any{"name": "World"}, &ferrs)
fmt.Println(out)
}
Output: Hello, World!
Index ¶
- Constants
- Variables
- func MemoizerForLocales(locales []string) map[string]any
- type Bundle
- func (b *Bundle) AddFunction(name string, fn Function)
- func (b *Bundle) AddResource(res *Resource) []error
- func (b *Bundle) AddResourceOverriding(res *Resource) []error
- func (b *Bundle) FormatPattern(pattern Pattern, args map[string]Value, errs *[]error) string
- func (b *Bundle) FormatPatternAny(pattern Pattern, args map[string]any, errs *[]error) string
- func (b *Bundle) GetMessage(id string) (*Message, bool)
- func (b *Bundle) HasMessage(id string) bool
- func (b *Bundle) Locale() string
- type BundleOption
- func WithDateTimeFormatter(f DateTimeFormatter) BundleOption
- func WithFunctions(fns map[string]Function) BundleOption
- func WithLocales(locales ...string) BundleOption
- func WithNumberFormatter(f NumberFormatter) BundleOption
- func WithPluralRules(p PluralRules) BundleOption
- func WithTransform(t TextTransform) BundleOption
- func WithUseIsolating(v bool) BundleOption
- type ComplexPattern
- type DateTime
- type DateTimeFormatter
- type DateTimeOptions
- type Expression
- type FluentString
- type Function
- type FunctionReference
- type Literal
- type Message
- type MessageReference
- type NamedArgument
- type None
- type Number
- type NumberFormatter
- type NumberLiteral
- type NumberOptions
- type Pattern
- type PatternElement
- type PluralRules
- type Resource
- type Scope
- type SelectExpression
- type StringLiteral
- type Term
- type TermReference
- type TextTransform
- type Value
- type VariableReference
- type Variant
Examples ¶
Constants ¶
const MaxPlaceables = 100
MaxPlaceables is the maximum number of placeables which can be expanded in a single FormatPattern call. The limit protects against the Billion Laughs and Quadratic Blowup attacks.
Variables ¶
var ( // ErrReference: an unknown message, term, variable, function, or attribute // was referenced. ErrReference = errors.New("fluent: reference error") // ErrRange: no variant matched a selector, a value is out of range, a // reference is cyclic, or the placeable limit was exceeded. ErrRange = errors.New("fluent: range error") // ErrType: a value cannot be used in the position it appears (e.g. a // non-numeric selector argument, or a term used as a placeable). ErrType = errors.New("fluent: type error") )
Error kinds collected by FormatPattern, mirroring the JS error classes fluent.js reports (ReferenceError / RangeError / TypeError). Every resolution error wraps one of these sentinels, so a caller can classify a failure with errors.Is, e.g. errors.Is(err, fluent.ErrReference).
Functions ¶
func MemoizerForLocales ¶
MemoizerForLocales returns a process-wide cache map shared by all bundles using the same locale list. Mirrors getMemoizerForLocale in fluent.js.
Types ¶
type Bundle ¶
type Bundle struct {
// contains filtered or unexported fields
}
Bundle is a single-language store of translation resources, responsible for formatting message values and attributes to strings.
A Bundle is safe for concurrent use: FormatPattern/FormatPatternAny, HasMessage, GetMessage, AddFunction, AddResource, and AddResourceOverriding may be called from multiple goroutines simultaneously. The messages, terms, and functions maps are guarded by mu; the locale and the injected formatters are set once at construction and never mutated afterwards.
Example (SelectExpression) ¶
ExampleBundle_selectExpression demonstrates a select expression. Without a real PluralRules implementation wired in, numeric selectors match by exact value, so the variant key "1" is selected for the number 1.
package main
import (
"fmt"
fluent "github.com/hakastein/gofluent"
)
func main() {
src := `
emails =
{ $count ->
[1] You have one new email.
*[other] You have { $count } new emails.
}
`
res, _ := fluent.NewResource(src)
b := fluent.NewBundle("en", fluent.WithUseIsolating(false))
b.AddResource(res)
msg, _ := b.GetMessage("emails")
var errs []error
fmt.Println(b.FormatPatternAny(msg.Value, map[string]any{"count": 1}, &errs))
fmt.Println(b.FormatPatternAny(msg.Value, map[string]any{"count": 5}, &errs))
}
Output: You have one new email. You have 5 new emails.
func NewBundle ¶
func NewBundle(locale string, opts ...BundleOption) *Bundle
NewBundle creates a Bundle for the given primary locale. useIsolating defaults to true; NUMBER and DATETIME are always available; the three formatters default to the dependency-free no-op implementations.
func (*Bundle) AddFunction ¶
AddFunction registers (or overrides) a runtime function by name.
func (*Bundle) AddResource ¶
AddResource adds a parsed resource to the bundle without allowing overrides. It returns errors for any attempted overrides of existing messages/terms.
func (*Bundle) AddResourceOverriding ¶
AddResourceOverriding adds a parsed resource, allowing it to override existing messages and terms.
func (*Bundle) FormatPattern ¶
FormatPattern formats a Pattern to a string. args resolves variable references; pass nil for none.
errs selects the error mode, mirroring fluent.js:
- Non-nil errs is collect mode: every resolution error is appended to *errs and a best-effort string is always returned (the resolver never panics).
- Nil errs is throw mode: the first resolution error is "thrown" (panics out of FormatPattern). The caller is responsible for recovering it. Use this only when you want strict failure rather than fault-tolerant rendering.
args accepts a map[string]Value (already-typed) — see FormatPatternAny for a map[string]any convenience wrapper.
func (*Bundle) FormatPatternAny ¶
FormatPatternAny is a convenience wrapper accepting raw Go argument values (map[string]any). Values are converted to Fluent Values via coerceArg.
Precision note: integer arguments are stored as float64 (Fluent's only numeric type, matching JS). int64/uint64 magnitudes above 2^53 cannot be represented exactly and may be rounded; pass a preformatted string (or a custom Value) when exact rendering of such large integers matters.
func (*Bundle) GetMessage ¶
GetMessage returns the raw message with the given id, if present.
func (*Bundle) HasMessage ¶
HasMessage reports whether a public message with the given id exists.
type BundleOption ¶
type BundleOption func(*Bundle)
BundleOption configures a Bundle in NewBundle.
func WithDateTimeFormatter ¶
func WithDateTimeFormatter(f DateTimeFormatter) BundleOption
WithDateTimeFormatter injects a DateTimeFormatter (replaces the no-op default).
func WithFunctions ¶
func WithFunctions(fns map[string]Function) BundleOption
WithFunctions registers additional builtin functions, merged over NUMBER and DATETIME.
func WithLocales ¶
func WithLocales(locales ...string) BundleOption
WithLocales sets the full locale fallback list. The first entry becomes the primary locale passed to formatters.
func WithNumberFormatter ¶
func WithNumberFormatter(f NumberFormatter) BundleOption
WithNumberFormatter injects a NumberFormatter (replaces the no-op default).
func WithPluralRules ¶
func WithPluralRules(p PluralRules) BundleOption
WithPluralRules injects a PluralRules implementation (replaces the no-op default).
func WithTransform ¶
func WithTransform(t TextTransform) BundleOption
WithTransform sets the text transform applied to string parts of patterns.
func WithUseIsolating ¶
func WithUseIsolating(v bool) BundleOption
WithUseIsolating sets whether to wrap interpolations in Unicode isolation marks (FSI/PDI). Default is true.
type ComplexPattern ¶
type ComplexPattern []PatternElement
ComplexPattern is an array of pattern elements.
type DateTime ¶
type DateTime struct {
// contains filtered or unexported fields
}
DateTime is a FluentType representing a date/time (FluentDateTime in fluent.js). It stores a time.Time plus an option bag passed to the DateTimeFormatter.
func NewDateTime ¶
func NewDateTime(value time.Time, opts DateTimeOptions) *DateTime
NewDateTime constructs a DateTime with the given time and options.
func (*DateTime) Opts ¶
func (d *DateTime) Opts() DateTimeOptions
Opts returns the datetime's formatting options.
type DateTimeFormatter ¶
type DateTimeFormatter interface {
FormatDateTime(locale string, t time.Time, opts DateTimeOptions) string
}
DateTimeFormatter renders a time to a string for a given locale and options.
type DateTimeOptions ¶
type DateTimeOptions struct {
Hour12 *bool
Weekday string
Era string
Year string
Month string
Day string
Hour string
Minute string
Second string
TimeZoneName string
DateStyle string
TimeStyle string
DayPeriod string
FractionalSecondDigits *int
Calendar string
NumberingSystem string
TimeZone string
}
DateTimeOptions carries the options that the DATETIME() builtin and FluentDateTime accept. It mirrors the subset of Intl.DateTimeFormatOptions used by fluent.js. Pointer fields distinguish "unset" from a zero value.
type Expression ¶
type Expression = any
Expression is the union of all placeable expression types. Concrete type is one of: *SelectExpression, *VariableReference, *TermReference, *MessageReference, *FunctionReference, *StringLiteral, *NumberLiteral.
type FluentString ¶
type FluentString string
FluentString is the FluentValue for a plain string (the JS string primitive).
func (FluentString) Format ¶
func (s FluentString) Format(_ *Scope) string
Format returns the string unchanged.
type Function ¶
Function is the signature of a Fluent builtin/runtime function. It receives positional and named Value arguments and returns a Value. Returning a non-nil error (or panicking) routes through the resolver's fault-tolerant error path, rendering `{NAME()}`. Mirrors FluentFunction in fluent.js.
type FunctionReference ¶
type FunctionReference struct {
Name string
Args []any // each element is an Expression or *NamedArgument
}
FunctionReference corresponds to ast.ts `FunctionReference` (type: "func").
type Literal ¶
type Literal = any
Literal is the union of StringLiteral and NumberLiteral. Concrete type is either *StringLiteral or *NumberLiteral.
type Message ¶
type Message struct {
ID string
Value Pattern // nil if the message has no value
Attributes map[string]Pattern
}
Message is the raw runtime message shape `{id, value, attributes}`.
type MessageReference ¶
MessageReference corresponds to ast.ts `MessageReference` (type: "mesg").
type NamedArgument ¶
NamedArgument corresponds to ast.ts `NamedArgument` (type: "narg").
type None ¶
type None struct {
// contains filtered or unexported fields
}
None is a FluentType representing no correct value (FluentNone in fluent.js). It renders missing references using the fluent.js fallback convention: a missing variable as `{$name}`, a missing message as `{message}`, a missing term as `{-term}`, a failed function as `{FUNC()}`. The default fallback is `???` which renders as `{???}`.
type Number ¶
type Number struct {
// contains filtered or unexported fields
}
Number is a FluentType representing a number (FluentNumber in fluent.js). It stores the numeric value plus an option bag passed to the NumberFormatter.
func NewNumber ¶
func NewNumber(value float64, opts NumberOptions) *Number
NewNumber constructs a Number with the given value and options.
func (*Number) Format ¶
Format renders the number using the bundle's NumberFormatter. A deferred option error is reported via the scope and the value falls back to its plain decimal rendering, mirroring Intl.NumberFormat throwing in fluent.js.
func (*Number) Opts ¶
func (n *Number) Opts() NumberOptions
Opts returns the number's formatting options.
type NumberFormatter ¶
type NumberFormatter interface {
FormatNumber(locale string, n float64, opts NumberOptions) string
}
NumberFormatter renders a number to a string for a given locale and options.
type NumberLiteral ¶
NumberLiteral corresponds to ast.ts `NumberLiteral` (type: "num").
type NumberOptions ¶
type NumberOptions struct {
Style string // "decimal" | "currency" | "percent" | "unit"
Currency string
CurrencyDisplay string
Unit string
UnitDisplay string
UseGrouping *bool
MinimumIntegerDigits *int
MinimumFractionDigits *int
MaximumFractionDigits *int
MinimumSignificantDigits *int
MaximumSignificantDigits *int
// Type selects the plural ruleset: "cardinal" (default) or "ordinal".
Type string
}
NumberOptions carries the options that the NUMBER() builtin and FluentNumber accept. It mirrors the subset of Intl.NumberFormatOptions used by fluent.js. Pointer fields distinguish "unset" from a zero value, mirroring how fluent.js merges option bags.
type Pattern ¶
type Pattern = any
Pattern is either a simple string or a complex pattern (slice of elements).
In fluent.js a Pattern is `string | Array<PatternElement>`. Go has no union type, so we model it as `any`, where the concrete value is either:
string – a simple pattern, or ComplexPattern – a complex pattern with placeables.
A nil Pattern represents the absence of a value (e.g. a message with only attributes).
type PatternElement ¶
type PatternElement = any
PatternElement is either a string (text run) or an Expression (placeable). Concrete type is either `string` or one of the Expression structs below.
type PluralRules ¶
type PluralRules interface {
// Cardinal returns the cardinal plural category for n.
Cardinal(locale string, n float64, opts NumberOptions) string
// Ordinal returns the ordinal plural category for n.
Ordinal(locale string, n float64, opts NumberOptions) string
}
PluralRules returns CLDR plural categories for a number in a given locale. Implementations return one of: "zero", "one", "two", "few", "many", "other".
type Resource ¶
type Resource struct {
Body []any
}
Resource is a structure storing parsed localization entries. Body holds *Message and *Term values, mirroring FluentResource.body in fluent.js.
func NewResource ¶
NewResource parses an FTL source into a Resource. Parse errors for individual messages are recovered (the message is skipped); the returned error slice is currently always nil-or-empty since the runtime parser silently skips broken entries, matching fluent.js. The signature returns it for API symmetry.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope stores the data required for a single pattern resolution and for error recovery. A new Scope is created per FormatPattern call on a complex pattern.
type SelectExpression ¶
type SelectExpression struct {
Selector Expression
Variants []Variant
Star int
}
SelectExpression corresponds to ast.ts `SelectExpression` (type: "select").
type StringLiteral ¶
type StringLiteral struct {
Value string
}
StringLiteral corresponds to ast.ts `StringLiteral` (type: "str").
type TermReference ¶
type TermReference struct {
Name string
Attr string // empty string means no attribute (ast.ts uses null)
Args []any // each element is an Expression or *NamedArgument
}
TermReference corresponds to ast.ts `TermReference` (type: "term").
type TextTransform ¶
TextTransform transforms the text parts of patterns. Mirrors TextTransform.
type Value ¶
type Value interface {
// Format renders this value to a string, optionally using the scope's
// pluggable formatters. Mirrors FluentType.toString(scope) in fluent.js.
Format(scope *Scope) string
}
Value is the base of Fluent's runtime type system. Every expression resolves to a Value. Callers convert a Value to its native string with Format.
type VariableReference ¶
type VariableReference struct {
Name string
}
VariableReference corresponds to ast.ts `VariableReference` (type: "var").
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cldr
|
|
|
datetime
Package datetime provides CLDR-based date/time formatting for Go, generated directly from the Unicode CLDR data (cldr-dates-full).
|
Package datetime provides CLDR-based date/time formatting for Go, generated directly from the Unicode CLDR data (cldr-dates-full). |
|
number
Package number provides CLDR-driven number, percent and currency formatting for Go, generated directly from the Unicode CLDR data (cldr-numbers-full and cldr-core).
|
Package number provides CLDR-driven number, percent and currency formatting for Go, generated directly from the Unicode CLDR data (cldr-numbers-full and cldr-core). |
|
plural
Package plural provides CLDR plural-rule selection (cardinal and ordinal) for Go, generated directly from the Unicode CLDR data.
|
Package plural provides CLDR plural-rule selection (cardinal and ordinal) for Go, generated directly from the Unicode CLDR data. |
|
Package fluentx provides locale-aware, CLDR-backed implementations of the pluggable formatting interfaces defined in the core fluent package (fluent.PluralRules, fluent.NumberFormatter, fluent.DateTimeFormatter).
|
Package fluentx provides locale-aware, CLDR-backed implementations of the pluggable formatting interfaces defined in the core fluent package (fluent.PluralRules, fluent.NumberFormatter, fluent.DateTimeFormatter). |
|
Package langneg is a faithful Go port of @fluent/langneg.
|
Package langneg is a faithful Go port of @fluent/langneg. |
|
Package localization is the high-level, synchronous localization layer for gofluent.
|
Package localization is the high-level, synchronous localization layer for gofluent. |
|
Package syntax is an idiomatic Go port of @fluent/syntax (Project Fluent): a parser, serializer, and AST visitor for the Fluent localization format.
|
Package syntax is an idiomatic Go port of @fluent/syntax (Project Fluent): a parser, serializer, and AST visitor for the Fluent localization format. |
|
ast
Package ast defines the Fluent abstract syntax tree, a faithful port of the data model from @fluent/syntax (ast.ts).
|
Package ast defines the Fluent abstract syntax tree, a faithful port of the data model from @fluent/syntax (ast.ts). |