functions

package
v0.4.18 Latest Latest
Warning

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

Go to latest
Published: Feb 18, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package functions provides MessageFormat 2.0 function implementations TypeScript original code: functions/ module

Index

Constants

View Source
const MaxOptionKeyLength = 100

MaxOptionKeyLength defines the maximum allowed length for an option key

View Source
const MaxOptionsCount = 50

MaxOptionsCount defines the maximum number of options allowed to prevent DoS

Variables

View Source
var DefaultFunctions = map[string]MessageFunction{
	"integer": IntegerFunction,
	"number":  NumberFunction,
	"string":  StringFunction,
	"offset":  OffsetFunction,
}

DefaultFunctions provides the built-in REQUIRED functions as defined in LDML 48 MessageFormat specification. Reference: https://www.unicode.org/reports/tr35/tr35-76/tr35-messageFormat.html#contents-of-part-9-messageformat

These functions are stable and covered by stability guarantees. They include: :integer, :number, :offset, and :string

TypeScript original code:

export let DefaultFunctions = {
  integer,
  number,
  string
};

DefaultFunctions = Object.freeze(

Object.assign(Object.create(null), DefaultFunctions)

);

View Source
var DraftFunctions = map[string]MessageFunction{
	"currency": CurrencyFunction,
	"date":     DateFunction,
	"datetime": DatetimeFunction,
	"math":     MathFunction,
	"percent":  PercentFunction,
	"time":     TimeFunction,
	"unit":     UnitFunction,
}

DraftFunctions provides functions classified as DRAFT by the LDML 48 MessageFormat specification. Reference: https://www.unicode.org/reports/tr35/tr35-76/tr35-messageFormat.html#contents-of-part-9-messageformat

These functions are liable to change and are NOT covered by stability guarantees.

Note: As of LDML 48, :currency and :percent have been finalized and are now stable. However, they remain in this collection for backward compatibility. The :unit function is still in DRAFT status.

TypeScript original code:

export let DraftFunctions = {
  currency,
  date,
  datetime,
  math,
  time,
  unit
};

DraftFunctions = Object.freeze(

Object.assign(Object.create(null), DraftFunctions)

);

View Source
var ErrNotBoolean = errors.New("not a boolean")

ErrNotBoolean indicates the value cannot be converted to a boolean.

View Source
var ErrNotPositiveInteger = errors.New("not a positive integer")

ErrNotPositiveInteger indicates the value cannot be converted to a non-negative integer.

View Source
var ErrNotString = errors.New("not a string")

ErrNotString indicates the value cannot be converted to a string.

View Source
var (
	ErrNotValidJSONNumber = errors.New("not a valid JSON number")
)

Static errors to avoid dynamic error creation

Functions

func CurrencyFunction

func CurrencyFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

CurrencyFunction implements the :currency function for currency value formatting.

Status: Stable (finalized in LDML 48) Specification: https://unicode.org/reports/tr35/tr35-messageFormat.html#currency

The :currency function formats numeric values as currency. It requires either: - An operand containing both a numeric value and currency code - A numeric operand with a currency option

Example:

{$amount :currency currency=USD}
{$price :currency currency=EUR fractionDigits=2}

TypeScript original code: export function currency(

ctx: MessageFunctionContext,
exprOpt: Record<string | symbol, unknown>,
operand?: unknown

): MessageNumber {
  const { source } = ctx;
  const input = readNumericOperand(operand, source);
  const options: MessageNumberOptions = Object.assign({}, input.options, {
    localeMatcher: ctx.localeMatcher,
    style: 'currency'
  } as const);

  for (const [name, optval] of Object.entries(exprOpt)) {
    if (optval === undefined) continue;
    try {
      switch (name) {
        case 'currency':
        case 'currencySign':
        case 'roundingMode':
        case 'roundingPriority':
        case 'trailingZeroDisplay':
        case 'useGrouping':
          options[name] = asString(optval);
          break;
        case 'minimumIntegerDigits':
        case 'minimumSignificantDigits':
        case 'maximumSignificantDigits':
        case 'roundingIncrement':
          options[name] = asPositiveInteger(optval);
          break;
        case 'currencyDisplay': {
          const strval = asString(optval);
          if (strval === 'never') {
            ctx.onError(
              new MessageResolutionError(
                'unsupported-operation',
                'Currency display "never" is not yet supported',
                source
              )
            );
          } else {
            options[name] = strval;
          }
          break;
        }
        case 'fractionDigits': {
          const strval = asString(optval);
          if (strval === 'auto') {
            options.minimumFractionDigits = undefined;
            options.maximumFractionDigits = undefined;
          } else {
            const numval = asPositiveInteger(strval);
            options.minimumFractionDigits = numval;
            options.maximumFractionDigits = numval;
          }
          break;
        }
      }
    } catch (error) {
      if (error instanceof MessageError) {
        ctx.onError(error);
      } else {
        const msg = `Value ${optval} is not valid for :currency option ${name}`;
        ctx.onError(new MessageResolutionError('bad-option', msg, source));
      }
    }
  }

  if (!options.currency) {
    const msg = 'A currency code is required for :currency';
    throw new MessageResolutionError('bad-operand', msg, source);
  }

  return getMessageNumber(ctx, input.value, options, false);
}

func DateFunction

func DateFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

DateFunction implements the :date function Formats only the date portion TypeScript reference: datetime.ts:66-70

func DatetimeFunction

func DatetimeFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

DatetimeFunction implements the :datetime function Formats both date and time portions TypeScript reference: datetime.ts:55-59

func FallbackFunction added in v0.2.0

func FallbackFunction(source string, locale string) messagevalue.MessageValue

FallbackFunction creates a fallback value for runtime/formatting errors

TypeScript original code:

export const fallback = (source: string = '�'): MessageFallback => ({
  type: 'fallback',
  source,
  toParts: () => [{ type: 'fallback', source }],
  toString: () => `{${source}}`
});

func GetFirstLocale added in v0.4.18

func GetFirstLocale(locales []string) string

GetFirstLocale returns the first locale from a list, or "en" as fallback

func IntegerFunction

func IntegerFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

IntegerFunction implements the :integer function for integer number formatting TypeScript original code: export function integer(

ctx: MessageFunctionContext,
exprOpt: Record<string, unknown>,
operand?: unknown

) {
  const input = readNumericOperand(operand, ctx.source);
  const value = Number.isFinite(input.value)
    ? Math.round(input.value as number)
    : input.value;
  const options: MessageNumberOptions = Object.assign({}, input.options, {
    localeMatcher: ctx.localeMatcher,
    maximumFractionDigits: 0
  } as const);
  for (const [name, optval] of Object.entries(exprOpt)) {
    if (optval === undefined) continue;
    try {
      switch (name) {
        case 'minimumIntegerDigits':
        case 'maximumSignificantDigits':
          options[name] = asPositiveInteger(optval);
          break;
        case 'select':
        case 'signDisplay':
        case 'useGrouping':
          options[name] = asString(optval);
      }
    } catch {
      const msg = `Value ${optval} is not valid for :integer option ${name}`;
      ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
    }
  }
  return getMessageNumber(ctx, value, options, true);
}

func MathFunction

func MathFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

MathFunction implements the :math function (DRAFT) math accepts a numeric value as input and adds or subtracts an integer value from it

TypeScript original code: export function math(

ctx: MessageFunctionContext,
exprOpt: Record<string | symbol, unknown>,
operand?: unknown

): MessageNumber {
  const { source } = ctx;
  let { value, options } = readNumericOperand(operand, source);

  let add: number;
  let sub: number;
  try {
    add = 'add' in exprOpt ? asPositiveInteger(exprOpt.add) : -1;
    sub = 'subtract' in exprOpt ? asPositiveInteger(exprOpt.subtract) : -1;
  } catch (error) {
    throw new MessageResolutionError('bad-option', String(error), source);
  }
  if (add < 0 === sub < 0) {
    const msg =
      'Exactly one of "add" or "subtract" is required as a :math option';
    throw new MessageResolutionError('bad-option', msg, source);
  }
  const delta = add < 0 ? -sub : add;
  if (typeof value === 'number') value += delta;
  else value += BigInt(delta);

  return number(ctx, {}, { valueOf: () => value, options });
}

func NumberFunction

func NumberFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

NumberFunction implements the :number function for numeric value formatting and selection.

Status: Stable (REQUIRED in LDML 48) Specification: https://www.unicode.org/reports/tr35/tr35-76/tr35-messageFormat.html#the-number-function

The :number function is a selector and formatter for numeric values.

Operand Requirements: - Must be a number, BigInt, or string representing a JSON number - Can be an object with valueOf() method and optional options map

Supported Options: - select: 'exact' | 'plural' | 'ordinal' (controls selection behavior) - minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits (digit size options) - minimumSignificantDigits, maximumSignificantDigits (digit size options) - roundingMode, roundingPriority, roundingIncrement (rounding control) - signDisplay, useGrouping, trailingZeroDisplay (formatting control)

Selection Behavior: - Exact numeric match is preferred over plural category - Supports plural rules (zero, one, two, few, many, other) - The select option must be set by a literal value, not a variable

TypeScript Reference: .reference/messageformat/mf2/messageformat/src/functions/number.ts

TypeScript original code: export function number(

ctx: MessageFunctionContext,
exprOpt: Record<string, unknown>,
operand?: unknown

): MessageNumber {
  const input = readNumericOperand(operand, ctx.source);
  const value = input.value;
  const options: MessageNumberOptions = Object.assign({}, input.options, {
    localeMatcher: ctx.localeMatcher,
    style: 'decimal'
  } as const);
  for (const [name, optval] of Object.entries(exprOpt)) {
    if (optval === undefined) continue;
    try {
      switch (name) {
        case 'minimumIntegerDigits':
        case 'minimumFractionDigits':
        case 'maximumFractionDigits':
        case 'minimumSignificantDigits':
        case 'maximumSignificantDigits':
        case 'roundingIncrement':
          options[name] = asPositiveInteger(optval);
          break;
        case 'roundingMode':
        case 'roundingPriority':
        case 'select':
        case 'signDisplay':
        case 'trailingZeroDisplay':
        case 'useGrouping':
          options[name] = asString(optval);
      }
    } catch {
      const msg = `Value ${optval} is not valid for :number option ${name}`;
      ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
    }
  }
  return getMessageNumber(ctx, value, options, true);
}

func OffsetFunction added in v0.3.6

func OffsetFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

OffsetFunction accepts a numeric value as input and adds or subtracts an integer value from it TypeScript original code: export function offset(

ctx: MessageFunctionContext,
exprOpt: Record<string | symbol, unknown>,
operand?: unknown

): MessageNumber {
  let { value, options } = readNumericOperand(operand);
  let add: number;
  try {
    add = 'add' in exprOpt ? asPositiveInteger(exprOpt.add) : -1;
  } catch {
    throw new MessageFunctionError(
      'bad-option',
      `Value ${exprOpt.add} is not valid for :offset option add`
    );
  }
  let sub: number;
  try {
    sub = 'subtract' in exprOpt ? asPositiveInteger(exprOpt.subtract) : -1;
  } catch {
    throw new MessageFunctionError(
      'bad-option',
      `Value ${exprOpt.subtract} is not valid for :offset option subtract`
    );
  }
  if (add < 0 === sub < 0) {
    const msg =
      'Exactly one of "add" or "subtract" is required as an :offset option';
    throw new MessageFunctionError('bad-option', msg);
  }
  const delta = add < 0 ? -sub : add;
  if (typeof value === 'number') value += delta;
  else value += BigInt(delta);
  return number(ctx, {}, { valueOf: () => value, options });
}

func PercentFunction added in v0.3.6

func PercentFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

PercentFunction implements the :percent function for percent value formatting.

Status: Stable (finalized in LDML 48) Specification: https://unicode.org/reports/tr35/tr35-messageFormat.html#percent

Important behavior: - When formatting or selecting, the numeric value is multiplied by 100 - The resolved value retains the original value (not multiplied) - Selection uses the multiplied value (e.g., 0.01 matches key "1", 1 matches key "100")

Example:

{0.5 :percent}  // Formats as "50%"
.match {$rate :percent}
100 {{Full rate}}  // Matches when $rate is 1.0

TypeScript original code: export function percent(

ctx: MessageFunctionContext,
exprOpt: Record<string | symbol, unknown>,
operand?: unknown

): MessageNumber {
  const input = readNumericOperand(operand);
  const options: MessageNumberOptions = Object.assign({}, input.options, {
    localeMatcher: ctx.localeMatcher,
    style: 'percent'
  } as const);

  for (const [name, optval] of Object.entries(exprOpt)) {
    if (optval === undefined) continue;
    try {
      switch (name) {
        case 'roundingMode':
        case 'roundingPriority':
        case 'signDisplay':
        case 'trailingZeroDisplay':
        case 'useGrouping':
          // @ts-expect-error Let Intl.NumberFormat construction fail
          options[name] = asString(optval);
          break;
        case 'minimumFractionDigits':
        case 'maximumFractionDigits':
        case 'minimumSignificantDigits':
        case 'maximumSignificantDigits':
          options[name] = asPositiveInteger(optval);
          break;
      }
    } catch {
      ctx.onError(
        'bad-option',
        `Value ${optval} is not valid for :percent option ${name}`
      );
    }
  }

  return getMessageNumber(ctx, input.value, options, true);
}

func SanitizeOptions added in v0.3.6

func SanitizeOptions(options map[string]any) map[string]any

SanitizeOptions creates a sanitized copy of options map, filtering out invalid keys. This is useful when you want to be permissive but still protect against malicious input.

Returns a new map containing only valid options.

func StringFunction

func StringFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

StringFunction implements the :string function TypeScript original code: export function string(

ctx: Pick<MessageFunctionContext, 'dir' | 'locales' | 'source'>,
_options: Record<string, unknown>,
operand?: unknown

): MessageString {
  const str = operand === undefined ? '' : String(operand);
  const selStr = str.normalize();
  return {
    type: 'string',
    source: ctx.source,
    dir: ctx.dir ?? 'auto',
    selectKey: keys => (keys.has(selStr) ? selStr : null),
    toParts() {
      const { dir } = ctx;
      const locale = ctx.locales[0];
      return dir === 'ltr' || dir === 'rtl'
        ? [{ type: 'string', dir, locale, value: str }]
        : [{ type: 'string', locale, value: str }];
    },
    toString: () => str,
    valueOf: () => str
  };
}

func TimeFunction

func TimeFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

TimeFunction implements the :time function Formats only the time portion TypeScript reference: datetime.ts:78-82

func UnitFunction

func UnitFunction(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

UnitFunction implements the :unit function (DRAFT) unit accepts as input numerical values as well as objects wrapping a numerical value that also include a unit property.

TypeScript original code: export function unit(

ctx: MessageFunctionContext,
exprOpt: Record<string | symbol, unknown>,
operand?: unknown

): MessageNumber {
  const input = readNumericOperand(operand, ctx.source);
  const options: MessageNumberOptions = Object.assign({}, input.options, {
    localeMatcher: ctx.localeMatcher,
    style: 'unit'
  } as const);

  for (const [name, optval] of Object.entries(exprOpt)) {
    if (optval === undefined) continue;
    try {
      switch (name) {
        case 'signDisplay':
        case 'roundingMode':
        case 'roundingPriority':
        case 'trailingZeroDisplay':
        case 'unit':
        case 'unitDisplay':
        case 'useGrouping':
          options[name] = asString(optval);
          break;
        case 'minimumIntegerDigits':
        case 'minimumFractionDigits':
        case 'maximumFractionDigits':
        case 'minimumSignificantDigits':
        case 'maximumSignificantDigits':
        case 'roundingIncrement':
          options[name] = asPositiveInteger(optval);
          break;
      }
    } catch (error) {
      if (error instanceof MessageError) {
        ctx.onError(error);
      } else {
        const msg = `Value ${optval} is not valid for :currency option ${name}`;
        ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
      }
    }
  }

  if (!options.unit) {
    const msg = 'A unit identifier is required for :unit';
    throw new MessageResolutionError('bad-operand', msg, ctx.source);
  }

  return getMessageNumber(ctx, input.value, options, false);
}

func UnknownFunction added in v0.2.0

func UnknownFunction(source string, input any, locale string) messagevalue.MessageValue

UnknownFunction creates an unknown value for unrecognized input

TypeScript original code: export const unknown = (

source: string,
input: unknown

): MessageUnknownValue => ({
  type: 'unknown',
  source,
  dir: 'auto',
  toParts: () => [{ type: 'unknown', value: input }],
  toString: () => String(input),
  valueOf: () => input
});

func ValidateOptionKey added in v0.3.6

func ValidateOptionKey(key string) error

ValidateOptionKey validates an option key name to prevent security issues. This function prevents potential injection attacks or malformed keys.

Security checks performed: 1. Key length validation (max 100 characters) 2. Character whitelist (alphanumeric, underscore, hyphen only) 3. Forbidden key names (dangerous JavaScript-like keys)

Reference: Inspired by TypeScript fix for prototype pollution (commit 82cd10b4) https://github.com/messageformat/messageformat/commit/82cd10b40e3f922f990bbcf88a6d14b70c0a3ce0

func ValidateOptions added in v0.3.6

func ValidateOptions(options map[string]any) error

ValidateOptions validates an entire options map. This prevents DoS attacks through excessive options and validates all keys.

Reference: Based on security best practices from TypeScript implementation

Types

type FunctionRegistry

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

FunctionRegistry manages function registration and lookup

func NewFunctionRegistry

func NewFunctionRegistry() *FunctionRegistry

NewFunctionRegistry creates a new function registry

func NewFunctionRegistryWithDraft

func NewFunctionRegistryWithDraft() *FunctionRegistry

NewFunctionRegistryWithDraft creates a new function registry including draft functions

func (*FunctionRegistry) Clone

func (fr *FunctionRegistry) Clone() *FunctionRegistry

Clone creates a copy of the registry

func (*FunctionRegistry) Get

func (fr *FunctionRegistry) Get(name string) (MessageFunction, bool)

Get retrieves a function from the registry

func (*FunctionRegistry) List

func (fr *FunctionRegistry) List() []string

List returns all registered function names

func (*FunctionRegistry) Merge

func (fr *FunctionRegistry) Merge(other *FunctionRegistry)

Merge merges another registry into this one

func (*FunctionRegistry) Register

func (fr *FunctionRegistry) Register(name string, fn MessageFunction)

Register adds a function to the registry

type MessageFunction

type MessageFunction func(
	ctx MessageFunctionContext,
	options map[string]any,
	operand any,
) messagevalue.MessageValue

MessageFunction represents a function that can be called within a message TypeScript original code: export type MessageFunction = (

ctx: MessageFunctionContext,
options: Record<string, unknown>,
operand?: unknown

) => MessageValue;

type MessageFunctionContext

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

MessageFunctionContext provides context for function execution TypeScript original code:

export class MessageFunctionContext {
  readonly dir: 'ltr' | 'rtl' | 'auto' | undefined;
  readonly id: string | undefined;
  readonly source: string;
  get literalOptionKeys(): Set<string>;
  get localeMatcher(): string;
  get locales(): string[];
  get onError(): (error: Error) => void;
}

func NewMessageFunctionContext

func NewMessageFunctionContext(
	locales []string,
	source string,
	localeMatcher string,
	onError func(error),
	literalOptionKeys map[string]bool,
	dir string,
	id string,
) MessageFunctionContext

NewMessageFunctionContext creates a new function context

func (MessageFunctionContext) Dir

func (ctx MessageFunctionContext) Dir() string

Dir returns the text direction override

func (MessageFunctionContext) ID

func (ctx MessageFunctionContext) ID() string

ID returns the unique identifier

func (MessageFunctionContext) LiteralOptionKeys

func (ctx MessageFunctionContext) LiteralOptionKeys() map[string]bool

LiteralOptionKeys returns the set of literal option keys

func (MessageFunctionContext) LocaleMatcher

func (ctx MessageFunctionContext) LocaleMatcher() string

LocaleMatcher returns the locale matcher strategy

func (MessageFunctionContext) Locales

func (ctx MessageFunctionContext) Locales() []string

Locales returns the available locales

func (MessageFunctionContext) OnError

func (ctx MessageFunctionContext) OnError(err error)

OnError calls the error handler

func (MessageFunctionContext) Source

func (ctx MessageFunctionContext) Source() string

Source returns the source string

type NumericInput

type NumericInput struct {
	Value   any
	Options map[string]any
}

NumericInput represents parsed numeric input with value and options TypeScript original code: { value: number | bigint; options: unknown }

Jump to

Keyboard shortcuts

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