fluent

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

README

gofluent

Go Reference CI Go Report Card License

A Go implementation of Project Fluent — a localization system for natural-sounding translations. You write .ftl files, load them into a per-locale Bundle, and format messages whose plurals, numbers, and dates follow the rules of each language. gofluent ports the reference JavaScript implementation (@fluent/syntax and @fluent/bundle); the locale-aware formatting is CLDR-backed by the github.com/hakastein/gocldr module (validated against Node's Intl.*) and wired into every bundle by default.

Status: pre-1.0. The library is feature-complete and tested against the upstream conformance and Intl.* suites, but the public API may still change between minor versions until 1.0.

Install

go get github.com/hakastein/gofluent

Requires Go 1.23 or newer.

Quickstart

This example renders one Russian message across the plural categories Russian actually uses — one (1, 21), few (2), many (5) — plus a grouped number and a localized date. It is verified by a runnable example (ExampleBundle_pluralRussian in example_test.go), so the output below is exactly what go test asserts.

package main

import (
	"fmt"
	"time"

	_ "github.com/hakastein/gocldr/locales/ru" // Russian number + date data (see "Locale data")

	fluent "github.com/hakastein/gofluent"
)

const src = `
apples =
    { $n ->
        [one] { $n } яблоко
        [few] { $n } яблока
       *[many] { $n } яблок
    }
total = Итого: { NUMBER($total) }
updated = Обновлено { DATETIME($at, dateStyle: "long") }
`

func main() {
	res, _ := fluent.NewResource(src)

	// NewBundle wires the CLDR plural rules, number, and date formatters by
	// default. useIsolating is disabled here so the output is plain text;
	// the default (true) wraps placeables in Unicode bidi isolation marks.
	b := fluent.NewBundle("ru", fluent.WithUseIsolating(false))
	b.AddResource(res)

	apples, _ := b.GetMessage("apples")
	for _, n := range []int{1, 2, 5, 21} {
		fmt.Println(b.FormatPatternAny(apples.Value, map[string]any{"n": n}, nil))
	}

	total, _ := b.GetMessage("total")
	fmt.Println(b.FormatPatternAny(total.Value, map[string]any{"total": 1234567}, nil))

	updated, _ := b.GetMessage("updated")
	at := time.Date(2023, 1, 5, 14, 9, 7, 0, time.UTC)
	fmt.Println(b.FormatPatternAny(updated.Value, map[string]any{"at": at}, nil))
}

Output:

1 яблоко
2 яблока
5 яблок
21 яблоко
Итого: 1 234 567
Обновлено 5 января 2023 г.

The number 1 234 567 is grouped with no-break spaces and the date reads 5 января 2023 г. — both Russian conventions, matching Intl.*. The plural select picks [one] for 1 and 21, [few] for 2, and the default *[many] for 5, following CLDR's Russian cardinal rules.

The same shape works for English — fluent.NewBundle("en") with an FTL whose select uses English's [one]/*[other] categories.

How it works

  1. A Bundle holds the translations for one locale (fluent.NewBundle("ru")). The locale string drives every locale-aware decision below.
  2. FTL source is parsed once with fluent.NewResource and added with b.AddResource. A resource is a set of messages; each message has a value (a Pattern) and optional .attributes.
  3. A { $n -> [one] … [few] … *[many] … } select expression asks the bundle's PluralRules for the CLDR plural category of $n in this locale, then renders the matching variant (falling back to the * default).
  4. NUMBER($n) and DATETIME($d) format their argument through the bundle's NumberFormatter / DateTimeFormatter, honoring options such as dateStyle: "long" or useGrouping.
  5. fluent.NewBundle installs those three formatters — the CLDR-backed plural rules, number, and date implementations — by default, matching ECMA-402 Intl.* (and therefore fluent.js). No opt-in step is needed. To override the default for a bundle, pass your own implementation through fluent.WithPluralRules / WithNumberFormatter / WithDateTimeFormatter.

CLDR-backed formatting is the default, so a bare fluent.NewBundle("en") already formats numbers, dates, and plurals locale-aware. The locale data that drives real number/date rendering is still opt-in — see "Locale data" below.

The resolver is fault-tolerant: pass a *[]error sink to FormatPattern / FormatPatternAny (or nil to panic on the first error). In collect mode it never panics — missing references and other problems are appended to the slice and rendered as fluent.js-style placeholders (for example {$name}), and a best-effort string is always returned. A Bundle is also safe for concurrent use across all of its read and Add* methods.

Locale data

Plural-category selection (the [one]/[few]/[many] choice) uses CLDR rules that are always linked — Russian plurals are correct with no extra import.

Number and date formatting data is opt-in: a program links only the locales it blank-imports. For each locale you format, import its data:

import _ "github.com/hakastein/gocldr/locales/ru" // numbers + dates for ru
import _ "github.com/hakastein/gocldr/locales/en" // numbers + dates for en

Each locales/<lang> package registers both the number and the date data for that language. (If you only ever format numbers, gocldr/number/locales/ru alone is enough; for dates, gocldr/datetime/locales/ru.) With no locale data imported, formatting degrades gracefully: dates render as RFC3339 and numbers use the ASCII root (e.g. 1,234,567), while plural selection still works.

The gocldr formatters are also usable on their own, independent of Fluent (gocldr/number, gocldr/plural, gocldr/datetime); see that module's docs.

Loading .ftl files

In a real app the FTL lives in files, one directory per locale, loaded through the localization package. localization.FSLoader accepts any fs.FS — typically an embed.FS (translations compiled into the binary) or os.DirFS("./locales") (read from disk at runtime).

Directory layout (the path template tells the loader where to look):

locales/
  en/
    main.ftl   # apples = { $n -> [one] … *[other] … }
  ru/
    main.ftl   # apples = { $n -> [one] … [few] … *[many] … }
import (
	"embed"

	_ "github.com/hakastein/gocldr/locales/en"
	_ "github.com/hakastein/gocldr/locales/ru"

	"github.com/hakastein/gofluent/localization"
)

//go:embed locales
var localesFS embed.FS

// "{locale}" and "{resource}" are substituted per (locale, resource) pair:
// e.g. "locales/ru/main.ftl".
loader := localization.FSLoader(localesFS, "locales/{locale}/{resource}.ftl")

l10n, _ := localization.NewFromLocales(
	[]string{"ru-RU"},     // requested locales (e.g. from Accept-Language)
	[]string{"ru", "en"},  // locales you ship
	"en",                  // default / ultimate fallback
	[]string{"main"},      // resource ids (file basenames)
	loader,
)

// Walks the negotiated chain (ru, then en) and returns the first match.
val, _ := l10n.FormatValue("apples", map[string]any{"n": 5}) // "5 яблок"

NewFromLocales negotiates the requested locales against the ones you ship, builds one Bundle per negotiated locale (each with the default CLDR-backed formatters), and resolves a message from the first bundle in the chain that defines it. Missing files and parse errors are non-fatal: the failing resource is skipped and the rest of the chain still works.

Packages

Package Purpose
github.com/hakastein/gofluent Runtime: fast FTL parser, fault-tolerant resolver, Bundle (one locale).
.../syntax (+ .../syntax/ast) Full AST, recursive-descent parser, serializer, visitor — for tooling.
.../langneg Language negotiation (port of @fluent/langneg).
.../localization High-level fallback layer that loads .ftl files and formats across an ordered chain of locale bundles.

Provenance & verification

gofluent is generated code — it was ported from fluent.js with the assistance of large language models — and that is stated plainly, because the project's credibility rests on verification rather than authorship. Correctness is pinned to executable references, all run under go test ./...:

  • The syntax parser and serializer are checked against the upstream Project Fluent conformance fixtures (62/62 structure, 35/36 reference — the single skip matches fluent.js).
  • The CLDR formatters live in github.com/hakastein/gocldr and are checked there against Node's Intl.* (Intl.PluralRules, Intl.NumberFormat, and Intl.DateTimeFormat golden fixtures).

Read the code and the tests, not just the prose — ARCHITECTURE.md explains the design and where each guarantee is enforced.

Contributing

Contributions are welcome. See CONTRIBUTING.md for build, test, and linting mechanics, and ARCHITECTURE.md for how the codebase is organized and why. By participating you agree to the Code of Conduct.

License

Licensed under the Apache License, Version 2.0. See NOTICE for attribution of the fluent.js port lineage and the CLDR data.

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.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"

	_ "github.com/hakastein/gocldr/locales/ru"
	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

Examples

Constants

View Source
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

View Source
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

func MemoizerForLocales(locales []string) map[string]any

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 (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") }
`
	res, errs := fluent.NewResource(src)
	if len(errs) > 0 {
		panic(errs[0])
	}

	// CLDR formatters are installed by default; useIsolating is disabled so the
	// output is plain text.
	b := fluent.NewBundle("ru", fluent.WithUseIsolating(false))
	b.AddResource(res)

	apples, _ := b.GetMessage("apples")
	for _, n := range []int{1, 2, 5, 21} {
		fmt.Println(b.FormatPatternAny(apples.Value, map[string]any{"n": n}, nil))
	}

	total, _ := b.GetMessage("total")
	fmt.Println(b.FormatPatternAny(total.Value, map[string]any{"total": 1234567}, nil))

	updated, _ := b.GetMessage("updated")
	at := time.Date(2023, 1, 5, 14, 9, 7, 0, time.UTC)
	fmt.Println(b.FormatPatternAny(updated.Value, map[string]any{"at": at}, nil))

}
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.
    }
`
	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 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

func (b *Bundle) AddFunction(name string, fn Function)

AddFunction registers (or overrides) a runtime function by name.

func (*Bundle) AddResource

func (b *Bundle) AddResource(res *Resource) []error

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

func (b *Bundle) AddResourceOverriding(res *Resource) []error

AddResourceOverriding adds a parsed resource, allowing it to override existing messages and terms.

func (*Bundle) FormatPattern

func (b *Bundle) FormatPattern(pattern Pattern, args map[string]Value, errs *[]error) string

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

func (b *Bundle) FormatPatternAny(pattern Pattern, args map[string]any, errs *[]error) string

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

func (b *Bundle) GetMessage(id string) (*Message, bool)

GetMessage returns the raw message with the given id, if present.

func (*Bundle) HasMessage

func (b *Bundle) HasMessage(id string) bool

HasMessage reports whether a public message with the given id exists.

func (*Bundle) Locale

func (b *Bundle) Locale() string

Locale returns the bundle's primary locale string.

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 CLDR-backed 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 CLDR-backed default).

func WithPluralRules

func WithPluralRules(p PluralRules) BundleOption

WithPluralRules injects a PluralRules implementation (replaces the CLDR-backed 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) Format

func (d *DateTime) Format(scope *Scope) string

Format renders the datetime using the bundle's DateTimeFormatter.

func (*DateTime) Opts

func (d *DateTime) Opts() DateTimeOptions

Opts returns the datetime's formatting options.

func (*DateTime) Value

func (d *DateTime) Value() time.Time

Value returns the wrapped time.Time.

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

type Function func(positional []Value, named map[string]Value) (Value, error)

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

type MessageReference struct {
	Name string
	Attr string // empty string means no attribute
}

MessageReference corresponds to ast.ts `MessageReference` (type: "mesg").

type NamedArgument

type NamedArgument struct {
	Name  string
	Value Literal // either *StringLiteral or *NumberLiteral
}

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 `{???}`.

func NewNone

func NewNone(value string) *None

NewNone constructs a None with the given fallback inner value.

func (*None) Format

func (n *None) Format(_ *Scope) string

Format renders the None as `{value}`.

func (*None) Value

func (n *None) Value() string

Value returns the raw fallback string (without braces).

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

func (n *Number) Format(scope *Scope) string

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.

func (*Number) Value

func (n *Number) Value() float64

Value returns the wrapped numeric value.

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

type NumberLiteral struct {
	Value     float64
	Precision int
}

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

func NewResource(source string) (*Resource, []error)

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 Term

type Term struct {
	ID         string
	Value      Pattern
	Attributes map[string]Pattern
}

Term is the raw runtime term shape `{id, value, attributes}`.

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

type TextTransform func(string) string

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").

type Variant

type Variant struct {
	Key   Literal // either *StringLiteral or *NumberLiteral
	Value Pattern
}

Variant corresponds to ast.ts `Variant`.

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, 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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL