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: where fluent.js relies on the JavaScript Intl.* objects, this port exposes locale-aware formatting (plural rules, numbers, dates) through pluggable interfaces. NewBundle installs CLDR-backed defaults — matching Intl.* — from the external github.com/hakastein/gocldr module; callers can override any of them with WithPluralRules, WithNumberFormatter and WithDateTimeFormatter.
Layers ¶
- Package fluent (this package): the runtime — a fast FTL parser (NewResource), a fault-tolerant resolver, and Bundle (one locale). NewBundle wires the CLDR-backed number/date/plural formatters by default.
- Package fluent/syntax: the full AST, recursive-descent parser, and serializer used by tooling and conformance.
- 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.
Locale-aware formatting is backed by github.com/hakastein/gocldr, whose output matches ECMA-402 Intl.*. Its tables are opt-in: blank-import the locale data you format, e.g. import _ "github.com/hakastein/gocldr/locales/en" (or .../locales/all). With none imported, formatting degrades to the CLDR root.
Basic use ¶
res := fluent.NewResource("hello = Hello, { $name }!")
b := fluent.NewBundle("en")
b.AddResource(res)
msg, _ := b.Message("hello")
out, errs := b.FormatPattern(msg.Value, map[string]any{"name": "World"})
The resolver is fault-tolerant: it never panics. Missing references and other problems are reported in the returned errors 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, Message, 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"
_ "github.com/hakastein/gocldr/locales/ru"
fluent "github.com/hakastein/gofluent"
)
func main() {
res := fluent.NewResource("hello = Hello, { $name }!")
// 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.Message("hello")
if !ok {
panic("message not found")
}
out, _ := b.FormatPattern(msg.Value, map[string]any{"name": "World"})
fmt.Println(out)
}
Output: Hello, World!
Index ¶
- Constants
- Variables
- 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]any) (result string, errs []error)
- func (b *Bundle) Locale() string
- func (b *Bundle) Message(id string) (*Message, bool)
- type DateTime
- type DateTimeFormatter
- type DateTimeOptions
- type Function
- type Message
- type None
- type Number
- type NumberFormatter
- type NumberOptions
- type Option
- func WithDateTimeFormatter(f DateTimeFormatter) Option
- func WithFunctions(fns map[string]Function) Option
- func WithLocales(locales ...string) Option
- func WithNumberFormatter(f NumberFormatter) Option
- func WithPluralRules(p PluralRules) Option
- func WithTransform(t TextTransform) Option
- func WithUseIsolating(v bool) Option
- type Pattern
- type PluralRules
- type Resource
- type Scope
- type String
- type TextTransform
- type Value
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 ¶
This section is empty.
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, Message, AddFunction, AddResource, and AddResourceOverriding may be called from multiple goroutines simultaneously. The locale and the injected formatters are set once at construction and never mutated afterwards.
Example (PluralRussian) ¶
ExampleBundle_pluralRussian shows a Russian bundle: the { $n -> [one] … [few] … *[many] … } select picks the correct CLDR plural category, and NUMBER()/ DATETIME() render with Russian conventions. CLDR formatting is the NewBundle default, so no extra wiring is needed.
The blank import _ "github.com/hakastein/gocldr/locales/ru" supplies Russian number and date data; CLDR plural rules are always linked, so the category selection (one/few/many) is correct even without it.
package main
import (
"fmt"
"time"
_ "github.com/hakastein/gocldr/locales/ru"
fluent "github.com/hakastein/gofluent"
)
func main() {
const src = `
apples =
{ $n ->
[one] { $n } яблоко
[few] { $n } яблока
*[many] { $n } яблок
}
total = Итого: { NUMBER($total) }
updated = Обновлено { DATETIME($at, dateStyle: "long") }
`
// CLDR formatters are installed by default; useIsolating is disabled so the
// output is plain text.
b := fluent.NewBundle("ru", fluent.WithUseIsolating(false))
b.AddResource(fluent.NewResource(src))
apples, _ := b.Message("apples")
for _, n := range []int{1, 2, 5, 21} {
out, _ := b.FormatPattern(apples.Value, map[string]any{"n": n})
fmt.Println(out)
}
total, _ := b.Message("total")
out, _ := b.FormatPattern(total.Value, map[string]any{"total": 1234567})
fmt.Println(out)
updated, _ := b.Message("updated")
at := time.Date(2023, 1, 5, 14, 9, 7, 0, time.UTC)
out, _ = b.FormatPattern(updated.Value, map[string]any{"at": at})
fmt.Println(out)
}
Output: 1 яблоко 2 яблока 5 яблок 21 яблоко Итого: 1 234 567 Обновлено 5 января 2023 г.
Example (SelectExpression) ¶
ExampleBundle_selectExpression demonstrates a select expression. A numeric selector matches a variant key by exact value before falling back to the CLDR plural category, so the literal key "1" is selected for the number 1 while 5 falls through to *[other].
package main
import (
"fmt"
_ "github.com/hakastein/gocldr/locales/ru"
fluent "github.com/hakastein/gofluent"
)
func main() {
src := `
emails =
{ $count ->
[1] You have one new email.
*[other] You have { $count } new emails.
}
`
b := fluent.NewBundle("en", fluent.WithUseIsolating(false))
b.AddResource(fluent.NewResource(src))
msg, _ := b.Message("emails")
one, _ := b.FormatPattern(msg.Value, map[string]any{"count": 1})
five, _ := b.FormatPattern(msg.Value, map[string]any{"count": 5})
fmt.Println(one)
fmt.Println(five)
}
Output: You have one new email. You have 5 new emails.
func NewBundle ¶
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 CLDR-backed implementations (matching Intl.*). Applications must blank-import the locale data they format, e.g. import _ "github.com/hakastein/gocldr/locales/ru" (or .../locales/all); with none imported, formatting degrades to the CLDR root / RFC 3339.
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 — a Message value or attribute — to a string. args resolves variable references; pass nil for none. Argument values may be strings, any integer or float type, time.Time, or a Value (e.g. a Number carrying formatting options); other types render as a missing-variable fallback with an error.
Formatting is fault-tolerant: a best-effort string is always returned, and every problem encountered (missing references, type mismatches, ...) is reported in errs, each classified by one of the ErrReference / ErrRange / ErrType sentinels.
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.
type DateTime ¶
type DateTime struct {
Time time.Time
Options DateTimeOptions
}
DateTime is the Value for a date/time (FluentDateTime in fluent.js): a time.Time plus the option bag passed to the DateTimeFormatter.
func NewDateTime ¶
func NewDateTime(t time.Time, opts DateTimeOptions) *DateTime
NewDateTime constructs a DateTime with the given time and 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 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 Message ¶
type Message struct {
ID string
Value Pattern // nil when the message has only attributes
Attributes map[string]Pattern
}
Message is a compiled message: its id, an optional value, and attributes. Treat it as read-only; it is shared by all formatting calls on the Bundle.
type None ¶
type None struct {
// contains filtered or unexported fields
}
None is the Value representing a missing or invalid value (FluentNone in fluent.js). It renders 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 `???`, rendering as `{???}`.
func NewNone ¶
NewNone constructs a None with the given fallback. An empty fallback defaults to "???".
type Number ¶
type Number struct {
Value float64
Options NumberOptions
// contains filtered or unexported fields
}
Number is the Value for a number (FluentNumber in fluent.js): a float64 plus the 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.
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 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 Option ¶ added in v0.4.0
type Option func(*Bundle)
Option configures a Bundle in NewBundle.
func WithDateTimeFormatter ¶
func WithDateTimeFormatter(f DateTimeFormatter) Option
WithDateTimeFormatter injects a DateTimeFormatter (replaces the CLDR-backed default).
func WithFunctions ¶
WithFunctions registers additional builtin functions, merged over NUMBER and DATETIME.
func WithLocales ¶
WithLocales sets the full locale fallback list. The first entry becomes the primary locale passed to formatters.
func WithNumberFormatter ¶
func WithNumberFormatter(f NumberFormatter) Option
WithNumberFormatter injects a NumberFormatter (replaces the CLDR-backed default).
func WithPluralRules ¶
func WithPluralRules(p PluralRules) Option
WithPluralRules injects a PluralRules implementation (replaces the CLDR-backed default).
func WithTransform ¶
func WithTransform(t TextTransform) Option
WithTransform sets the text transform applied to string parts of patterns.
func WithUseIsolating ¶
WithUseIsolating sets whether to wrap interpolations in Unicode isolation marks (FSI/PDI). Default is true.
type Pattern ¶
type Pattern interface {
// contains filtered or unexported methods
}
Pattern is a compiled message value or attribute. Obtain one from a Message (its Value field or an Attributes entry) and render it with Bundle.FormatPattern. Pattern is opaque: all implementations live in this package. A nil Pattern represents the absence of a value (e.g. a message with only attributes).
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 {
// contains filtered or unexported fields
}
Resource is a parsed set of FTL entries, ready to be added to a Bundle with AddResource. It mirrors FluentResource in fluent.js.
func NewResource ¶
NewResource parses an FTL source into a Resource. The runtime parser is fault-tolerant, matching fluent.js: entries that fail to parse are silently skipped. Use the syntax package to diagnose malformed sources.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope carries the state of a single pattern resolution: the bundle, the caller's arguments, and the errors collected so far. Library users only meet it as the argument to Value.Format; its only public surface is Locale.
type TextTransform ¶
TextTransform transforms the text parts of patterns.
type Value ¶
type Value interface {
// Format renders this value to a string. The scope carries the bundle's
// locale and formatters; it may be nil when formatting outside a
// resolution (implementations must tolerate that).
Format(scope *Scope) string
}
Value is the base of Fluent's runtime type system. Every expression resolves to a Value; Format renders it to its final string. Custom argument types implement Value directly (mirroring user subclasses of FluentType in fluent.js).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
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 localization layer for gofluent.
|
Package localization is the high-level 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). |