messageformat

package module
v0.4.3 Latest Latest
Warning

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

Go to latest
Published: Sep 18, 2025 License: MIT Imports: 12 Imported by: 0

README

MessageFormat Go

Go Reference Go Report Card

A comprehensive Go library providing two implementations of MessageFormat for internationalization:

  • 🆕 MessageFormat 2.0 (this package): Latest Unicode specification with advanced features
  • 🔒 ICU MessageFormat v1 (v1 package): Legacy ICU-compatible implementation

📋 Version Guide

Version Package Path Specification Status
V2 (Recommended) github.com/kaptinlin/messageformat-go Unicode MessageFormat 2.0 ✅ Production Ready
V1 (Legacy) github.com/kaptinlin/messageformat-go/v1 ICU MessageFormat ✅ Maintained

💡 New projects should use V2. Existing projects can continue using V1 or migrate to V2.

📖 V1 Documentation: See v1/README.md for ICU MessageFormat documentation.


MessageFormat 2.0 Implementation

A production-ready Go implementation of the Unicode MessageFormat 2.0 specification, providing comprehensive internationalization (i18n) capabilities with advanced features like pluralization, gender selection, bidirectional text support, and custom formatting functions.

🏆 Specification Compliance

This implementation passes the official MessageFormat 2.0 test suite from the Unicode Consortium.

🚀 Quick Start

Installation
go get github.com/kaptinlin/messageformat-go

Requirements: Go 1.21 or later

Basic Example
package main

import (
    "fmt"
    "github.com/kaptinlin/messageformat-go"
)

func main() {
    // Create a MessageFormat instance
    mf, err := messageformat.New("en", "Hello, {$name}!")
    if err != nil {
        panic(err)
    }

    // Format the message
    result, err := mf.Format(map[string]interface{}{
        "name": "World",
    })
    if err != nil {
        panic(err)
    }

    fmt.Println(result) // Output: Hello, World!
    // Note: Clean output by default (BidiIsolation: BidiNone)
    // For RTL support: messageformat.WithBidiIsolation(messageformat.BidiDefault)
}
With Bidirectional Text Isolation (for RTL languages)
// For proper RTL support with bidi isolation characters
mf, err := messageformat.New("ar", "مرحبا {$name}!", 
    messageformat.WithBidiIsolation(messageformat.BidiDefault))
    
result, err := mf.Format(map[string]interface{}{
    "name": "عالم",
})
// Output: مرحبا ⁨عالم⁩!
// Note: ⁨⁩ are Unicode bidi isolation characters for proper RTL display

✨ Key Features

🌍 MessageFormat 2.0 Support
  • Pattern Matching: Advanced .match statements with exact number and plural category matching
  • Variable Declarations: .input and .local declarations with function annotations
  • Standard Functions: Built-in :number, :integer, :string, and :datetime formatting
  • Custom Functions: Extensible function system with locale awareness
  • Markup Support: {#tag}, {/tag}, {#tag /} syntax support
  • Unicode Compliance: Unicode normalization and bidirectional text handling
📝 Syntax Guidelines

Important: Variables must use $ prefix and selectors must be properly declared:

// ❌ Incorrect - missing $ prefix and selector declaration
mf, err := messageformat.New("en", `.match {count}
one {{One item}}
*   {{Many items}}`)

// ✅ Correct - proper variable syntax and selector declaration
mf, err := messageformat.New("en", `
.input {$count :number}
.match $count
one {{One item}}
*   {{Many items}}`)

Key Rules:

  • Variables: Always use {$variableName} syntax
  • Selectors: Declare with .input {$var :function} before .match
  • Match statements: Use .match $var (without braces)
🌐 International Features
  • Multi-Locale Support: Intelligent locale fallback and negotiation
  • Automatic Direction Detection: RTL/LTR detection for 25+ languages
  • Bidirectional Text Isolation: Configurable Unicode bidi isolation
  • Locale-Aware Formatting: Currency, numbers, dates, and percentages adapt to locale conventions
  • Mixed Content Handling: Proper LTR/RTL text mixing in complex layouts
🛡️ Production Ready
  • Thread-Safe: Safe for concurrent use after construction
  • Graceful Error Handling: Fallback representations for missing variables
  • Performance Optimized: Efficient parsing and formatting algorithms
  • TypeScript Compatible: API designed to match the TypeScript implementation
  • Testing: 100+ test cases covering specification compliance

📖 Documentation

Guide Description
Getting Started Installation, basic concepts, and first steps
Message Syntax MessageFormat 2.0 syntax reference
API Reference API documentation with examples
Formatting Functions Built-in and custom function development
Custom Functions Advanced function development guide
Error Handling Error handling strategies

🎯 Usage Examples

Number Formatting with Localization
mf, err := messageformat.New("de-DE", 
    "Preis: {$amount :number style=currency currency=EUR}")

result, err := mf.Format(map[string]interface{}{
    "amount": 1234.56,
})
// Output: "Preis: €1,234.56" (actual format may vary by locale implementation)
Advanced Pluralization
mf, err := messageformat.New("en", `
.input {$count :number}
.match $count
0   {{No items in your cart}}
1   {{One item in your cart}}
*   {{{$count} items in your cart}}
`)

result, err := mf.Format(map[string]interface{}{
    "count": 5,
})
// Output: "5 items in your cart"
Multi-Selector Pattern Matching
mf, err := messageformat.New("en", `
.input {$photoCount :number}
.input {$userGender :string}
.match $photoCount $userGender
0   *     {{{$userName} has no photos}}
1   male  {{{$userName} has one photo in his album}}
1   *     {{{$userName} has one photo in her album}}
*   male  {{{$userName} has {$photoCount} photos in his album}}
*   *     {{{$userName} has {$photoCount} photos in her album}}
`)
Custom Functions with Locale Support
import (
    "strings"
    "github.com/kaptinlin/messageformat-go/pkg/functions"
    "github.com/kaptinlin/messageformat-go/pkg/messagevalue"
)

func customUppercase(ctx functions.MessageFunctionContext, options map[string]interface{}, input interface{}) messagevalue.MessageValue {
    locales := ctx.Locales()
    locale := "en"
    if len(locales) > 0 {
        locale = locales[0]
    }
    
    str := fmt.Sprintf("%v", input)
    return messagevalue.NewStringValue(strings.ToUpper(str), locale, ctx.Source())
}

mf, err := messageformat.New("en", "Hello, {$name :uppercase}!",
    messageformat.WithFunction("uppercase", customUppercase),
)
Structured Output for Rich Text
parts, err := mf.FormatToParts(map[string]interface{}{
    "name": "World",
    "count": 42,
})

for _, part := range parts {
    switch p := part.(type) {
    case *messageformat.MessageTextPart:
        fmt.Printf("Text: %s\n", p.Value())
    case *messageformat.MessageNumberPart:
        fmt.Printf("Number: %s (locale: %s)\n", p.Value(), p.Locale())
    case *messageformat.MessageStringPart:
        fmt.Printf("Variable: %s\n", p.Value())
    }
}

🎛️ Configuration Options

For most production applications, use this simplified configuration:

import (
    "github.com/kaptinlin/messageformat-go"
    "github.com/kaptinlin/messageformat-go/pkg/functions"
)

// Recommended production setup - simple and clean output
mf, err := messageformat.New("en", "Welcome, {$name}!", &messageformat.MessageFormatOptions{
    BidiIsolation: messageformat.BidiNone,        // Clean output without Unicode control chars
    LocaleMatcher: messageformat.LocaleBestFit,   // Best locale matching
    Functions:     functions.DraftFunctions,      // Include all formatting functions
})
Functional Options (Alternative)
mf, err := messageformat.New("ar", "مرحبا {$name}!",
    messageformat.WithBidiIsolation("none"),      // Simplified for most use cases
    messageformat.WithDir("rtl"),
    messageformat.WithFunction("custom", myCustomFunction),
)
Traditional Options Structure
mf, err := messageformat.New("en", "Hello, {$name}!", &messageformat.MessageFormatOptions{
    BidiIsolation: messageformat.BidiNone,
    Dir:          messageformat.DirLTR,
    Functions:    map[string]messageformat.MessageFunction{
        "custom": myCustomFunction,
    },
})
TypeScript Mapping Guide

Key Differences from TypeScript Implementation:

Feature TypeScript Default Go Default Rationale
bidiIsolation 'default' (enabled) BidiNone (disabled) KISS principle - simpler output
Variable syntax {name} {$name} MessageFormat 2.0 spec requirement
API style Object properties Methods + options struct Go idioms
// TypeScript (default behavior)
const mf = new MessageFormat('en', 'Hello, {name}!');
const result = mf.format({ name: 'World' });
// Output: "Hello, ⁨World⁩!" (with bidi isolation)
// Go equivalent (clean output by default)
mf, err := messageformat.New("en", "Hello, {$name}!")
result, err := mf.Format(map[string]interface{}{
    "name": "World",
})
// Output: "Hello, World!" (clean, no bidi isolation)

// To match TypeScript default behavior:
mf, err := messageformat.New("en", "Hello, {$name}!", 
    messageformat.WithBidiIsolation(messageformat.BidiDefault))

🧪 Testing & Verification

Prerequisites

Initialize git submodules to fetch the official test suite:

# Clone with submodules
git clone --recurse-submodules https://github.com/kaptinlin/messageformat-go.git

# Or initialize submodules after cloning
git submodule update --init --recursive
Running Tests
# Run all tests including official test suite
make test

# Run unit tests only (excluding official test suite)
make test-unit

# Run official MessageFormat 2.0 test suite only
make test-official

# Run tests with coverage report
make test-coverage

# Run benchmarks
make bench
Development Workflow
# Show all available commands
make help

# Format code and run all checks
make verify

# Run examples to verify functionality
make examples

📋 For detailed testing instructions, see TESTING.md

🌐 Features

Unicode Features
  • Bidirectional Text: Unicode Bidirectional Algorithm support
  • Text Isolation: Configurable bidi isolation (auto, none, always)
  • Normalization: Unicode normalization for consistent text handling
  • Mixed Scripts: Proper handling of mixed LTR/RTL content

🤝 Contributing

We welcome contributions! Please see CONTRIBUTING.md for detailed guidelines on:

  • Development setup and workflow
  • Code standards and testing requirements
  • Commit message conventions (Conventional Commits)
  • Pull request process

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

This Go implementation is inspired by the MessageFormat JavaScript/TypeScript library and follows the official Unicode MessageFormat 2.0 specification.

Special thanks to:

  • The Unicode MessageFormat Working Group for their work on internationalization standards
  • The Unicode Consortium for maintaining the specification
  • The open-source community for their contributions and feedback

Ready to internationalize your Go applications? Start with our Getting Started Guide or explore the API Reference for advanced usage patterns.

Documentation

Overview

Package messageformat provides the main MessageFormat 2.0 API

Package messageformat provides the main MessageFormat 2.0 API

Package messageformat provides functional options for MessageFormat configuration

Index

Constants

View Source
const (
	DirLTR  = bidi.DirLTR
	DirRTL  = bidi.DirRTL
	DirAuto = bidi.DirAuto
)

Re-export constants from bidi package for API compatibility

Variables

View Source
var (
	ParseMessage     = datamodel.ParseMessage
	StringifyMessage = datamodel.StringifyMessage
	Validate         = datamodel.ValidateMessage
	Visit            = datamodel.Visit
)

Data model operations - matches TypeScript exports

View Source
var (
	IsExpression     = datamodel.IsExpression
	IsFunctionRef    = datamodel.IsFunctionRef
	IsLiteral        = datamodel.IsLiteral
	IsMarkup         = datamodel.IsMarkup
	IsMessage        = datamodel.IsMessage
	IsPatternMessage = datamodel.IsPatternMessage
	IsSelectMessage  = datamodel.IsSelectMessage
	IsVariableRef    = datamodel.IsVariableRef
	IsCatchallKey    = datamodel.IsCatchallKey
)

Type guards - matches TypeScript exports

View Source
var DefaultFunctions = functions.DefaultFunctions

DefaultFunctions provides access to built-in functions

View Source
var DraftFunctions = functions.DraftFunctions

DraftFunctions provides access to draft functions (beta)

Functions

This section is empty.

Types

type BidiIsolation added in v0.2.0

type BidiIsolation string

BidiIsolation represents the bidi isolation strategy

const (
	BidiDefault BidiIsolation = "default"
	BidiNone    BidiIsolation = "none"
)

type Direction added in v0.2.0

type Direction = bidi.Direction

Direction represents text direction Use the Direction type from bidi package as the authoritative definition

type FormatOption

type FormatOption func(*FormatOptions)

FormatOption represents a functional option for Format methods

func WithErrorHandler

func WithErrorHandler(handler func(error)) FormatOption

WithErrorHandler sets an error handler for Format methods TypeScript original code: format(msgParams?: Record<string, unknown>, onError?: (error: Error) => void): string

type FormatOptions

type FormatOptions struct {
	OnError func(error)
}

FormatOptions represents options for Format and FormatToParts methods

type LocaleMatcher added in v0.2.0

type LocaleMatcher string

LocaleMatcher represents locale matching strategy

const (
	LocaleBestFit LocaleMatcher = "best fit"
	LocaleLookup  LocaleMatcher = "lookup"
)

type MessageFormat

type MessageFormat struct {
	// contains filtered or unexported fields
}

MessageFormat represents a compiled MessageFormat 2.0 message TypeScript original code:

export class MessageFormat<T extends string = never, P extends string = T> {
  readonly #bidiIsolation: boolean;
  readonly #dir: 'ltr' | 'rtl' | 'auto';
  readonly #localeMatcher: 'best fit' | 'lookup';
  readonly #locales: Intl.Locale[];
  readonly #message: Message;
  readonly #functions: Record<string, MessageFunction<T | DefaultFunctionTypes, P | DefaultFunctionTypes>>;
  constructor(locales, source, options) { ... }
  format(msgParams, onError) { ... }
  formatToParts(msgParams, onError) { ... }
}

func MustNew added in v0.3.0

func MustNew(
	locales interface{},
	source interface{},
	options ...interface{},
) *MessageFormat

MustNew creates a new MessageFormat and panics if there's an error This is a convenience function for cases where you're certain the input is valid

func New

func New(
	locales interface{},
	source interface{},
	options ...interface{},
) (*MessageFormat, error)

New creates a new MessageFormat from locales, source, and options Supports both traditional options struct and functional options pattern TypeScript original code: constructor(

locales: string | string[] | undefined,
source: string | Message,
options?: MessageFormatOptions<T, P>

) {
  this.#bidiIsolation = options?.bidiIsolation !== 'none';
  this.#localeMatcher = options?.localeMatcher ?? 'best fit';
  this.#locales = Array.isArray(locales) ? locales.map(lc => new Intl.Locale(lc)) : locales ? [new Intl.Locale(locales)] : [];
  this.#dir = options?.dir ?? getLocaleDir(this.#locales[0]);
  this.#message = typeof source === 'string' ? parseMessage(source) : source;
  validate(this.#message);
  this.#functions = options?.functions ? Object.assign(Object.create(null), DefaultFunctions, options.functions) : DefaultFunctions;
}

func (*MessageFormat) BidiIsolation added in v0.2.0

func (mf *MessageFormat) BidiIsolation() bool

BidiIsolation returns whether bidi isolation is enabled

func (*MessageFormat) Dir added in v0.2.0

func (mf *MessageFormat) Dir() string

Dir returns the message's base direction

func (*MessageFormat) Format

func (mf *MessageFormat) Format(
	values map[string]interface{},
	options ...interface{},
) (string, error)

Format formats the message with the given values and optional error handler Supports both traditional onError callback and functional options pattern

func (*MessageFormat) FormatToParts

func (mf *MessageFormat) FormatToParts(
	values map[string]interface{},
	options ...interface{},
) ([]messagevalue.MessagePart, error)

FormatToParts formats the message and returns detailed parts Supports both traditional onError callback and functional options pattern

func (*MessageFormat) ResolvedOptions added in v0.3.0

func (mf *MessageFormat) ResolvedOptions() ResolvedMessageFormatOptions

ResolvedOptions returns the resolved options for this MessageFormat instance This method is required by the TC39 Intl.MessageFormat proposal https://github.com/tc39/proposal-intl-messageformat#constructor-options-and-resolvedoptions

type MessageFormatOptions

type MessageFormatOptions struct {
	// The bidi isolation strategy for message formatting.
	// "default" isolates all expression placeholders except when both message and placeholder are LTR.
	// "none" applies no isolation at all.
	BidiIsolation BidiIsolation `json:"bidiIsolation,omitempty"`

	// Explicitly set the message's base direction.
	// If not set, the direction is detected from the primary locale.
	Dir Direction `json:"dir,omitempty"`

	// Locale matching algorithm for multiple locales.
	LocaleMatcher LocaleMatcher `json:"localeMatcher,omitempty"`

	// Custom functions to make available during message resolution.
	// Extends the default functions.
	Functions map[string]functions.MessageFunction `json:"functions,omitempty"`

	// Logger for this MessageFormat instance. If nil, uses global logger.
	Logger *slog.Logger `json:"-"`
}

MessageFormatOptions represents options for creating a MessageFormat TypeScript original code: export interface MessageFormatOptions<

T extends string = never,
P extends string = T

> {
  bidiIsolation?: 'default' | 'none';
  dir?: 'ltr' | 'rtl' | 'auto';
  localeMatcher?: 'best fit' | 'lookup';
  functions?: Record<string, MessageFunction<T, P>>;
}

func NewMessageFormatOptions added in v0.2.0

func NewMessageFormatOptions(opts *MessageFormatOptions) *MessageFormatOptions

NewMessageFormatOptions creates a new MessageFormatOptions with defaults

type Option

type Option func(*MessageFormatOptions)

Option represents a functional option for MessageFormat constructor TypeScript original code: MessageFormatOptions interface

func WithBidiIsolation

func WithBidiIsolation(strategy BidiIsolation) Option

WithBidiIsolation sets the bidi isolation strategy TypeScript original code: bidiIsolation?: 'default' | 'none';

func WithBidiIsolationString added in v0.2.0

func WithBidiIsolationString(strategy string) Option

WithBidiIsolationString sets the bidi isolation strategy from string (for backward compatibility)

func WithDir

func WithDir(direction Direction) Option

WithDir sets the message's base direction TypeScript original code: dir?: 'ltr' | 'rtl' | 'auto';

func WithDirString added in v0.2.0

func WithDirString(direction string) Option

WithDirString sets the message's base direction from string (for backward compatibility)

func WithFunction

func WithFunction(name string, fn functions.MessageFunction) Option

WithFunction adds a single custom function TypeScript original code: functions?: Record<string, MessageFunction<T, P>>;

func WithFunctions

func WithFunctions(funcs map[string]functions.MessageFunction) Option

WithFunctions adds multiple custom functions TypeScript original code: functions?: Record<string, MessageFunction<T, P>>;

func WithLocaleMatcher

func WithLocaleMatcher(matcher LocaleMatcher) Option

WithLocaleMatcher sets the locale matching algorithm TypeScript original code: localeMatcher?: 'best fit' | 'lookup';

func WithLocaleMatcherString added in v0.2.0

func WithLocaleMatcherString(matcher string) Option

WithLocaleMatcherString sets the locale matching algorithm from string (for backward compatibility)

func WithLogger added in v0.1.2

func WithLogger(logger *slog.Logger) Option

WithLogger sets a custom logger for this MessageFormat instance

type ResolvedMessageFormatOptions added in v0.3.0

type ResolvedMessageFormatOptions struct {
	BidiIsolation BidiIsolation                        `json:"bidiIsolation"`
	Dir           Direction                            `json:"dir"`
	Functions     map[string]functions.MessageFunction `json:"functions"`
	LocaleMatcher LocaleMatcher                        `json:"localeMatcher"`
}

ResolvedMessageFormatOptions represents the resolved options for a MessageFormat instance Based on TC39 Intl.MessageFormat proposal https://github.com/tc39/proposal-intl-messageformat#constructor-options-and-resolvedoptions

Directories

Path Synopsis
examples
advanced command
basic command
pluralization command
internal
cst
Package cst provides expression parsing for CST TypeScript original code: cst/expression.ts module
Package cst provides expression parsing for CST TypeScript original code: cst/expression.ts module
resolve
Package resolve provides expression resolution for MessageFormat 2.0 TypeScript original code: format-context.ts module
Package resolve provides expression resolution for MessageFormat 2.0 TypeScript original code: format-context.ts module
selector
Package selector provides pattern selection for MessageFormat 2.0 TypeScript original code: select-pattern.ts module
Package selector provides pattern selection for MessageFormat 2.0 TypeScript original code: select-pattern.ts module
pkg
bidi
Package bidi provides bidirectional text direction utilities for MessageFormat 2.0 TypeScript original code: dir-utils.ts module
Package bidi provides bidirectional text direction utilities for MessageFormat 2.0 TypeScript original code: dir-utils.ts module
datamodel
Package datamodel provides CST to data model conversion TypeScript original code: data-model/from-cst.ts module
Package datamodel provides CST to data model conversion TypeScript original code: data-model/from-cst.ts module
errors
Package errors provides error types for MessageFormat 2.0 implementation Following TypeScript errors.ts module with Go best practices
Package errors provides error types for MessageFormat 2.0 implementation Following TypeScript errors.ts module with Go best practices
functions
Package functions provides MessageFormat 2.0 function implementations TypeScript original code: functions/ module
Package functions provides MessageFormat 2.0 function implementations TypeScript original code: functions/ module
logger
Package logger provides simple structured logging for MessageFormat 2.0
Package logger provides simple structured logging for MessageFormat 2.0
messagevalue
Package messagevalue provides message value interfaces and implementations for MessageFormat 2.0 TypeScript original code: message-value.ts module
Package messagevalue provides message value interfaces and implementations for MessageFormat 2.0 TypeScript original code: message-value.ts module
parts
Package parts provides formatted parts types for MessageFormat 2.0 TypeScript original code: formatted-parts.ts module
Package parts provides formatted parts types for MessageFormat 2.0 TypeScript original code: formatted-parts.ts module
v1
Package v1 provides MessageFormat v1 (ICU MessageFormat) implementation for Go
Package v1 provides MessageFormat v1 (ICU MessageFormat) implementation for Go
examples/basic command
Package main demonstrates basic MessageFormat usage.
Package main demonstrates basic MessageFormat usage.
examples/ecommerce command
Package main demonstrates real-world e-commerce MessageFormat usage.
Package main demonstrates real-world e-commerce MessageFormat usage.
examples/multilingual command
Package main demonstrates multilingual MessageFormat usage with different locales.
Package main demonstrates multilingual MessageFormat usage with different locales.
examples/performance command
Package main demonstrates MessageFormat performance optimization techniques.
Package main demonstrates MessageFormat performance optimization techniques.

Jump to

Keyboard shortcuts

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