Class: Foxtail::Bundle

Inherits:
Object
  • Object
show all
Defined in:
lib/foxtail/bundle.rb,
lib/foxtail/bundle/scope.rb,
lib/foxtail/bundle/parser.rb,
lib/foxtail/bundle/resolver.rb,
lib/foxtail/bundle/parser/ast.rb

Overview

Main runtime class for message formatting and localization.

Bundle manages a collection of messages and terms for a single locale, providing formatting capabilities with support for pluralization, variable interpolation, and function calls.

ICU4X handles locale fallback internally through the locale’s parent chain (e.g., ja-JP → ja → und), so only a single locale is needed.

Corresponds to fluent-bundle/src/bundle.ts in the original JavaScript implementation.

Examples:

Basic usage

locale = ICU4X::Locale.parse("en-US")
bundle = Foxtail::Bundle.new(locale)

resource = Foxtail::Resource.from_string("hello = Hello, {$name}!")
bundle.add_resource(resource)

result = bundle.format("hello", name: "World")
# => "Hello, World!"

With custom functions (auto-merged with defaults)

bundle = Foxtail::Bundle.new(locale, functions: {
  "UPPER" => ->(str, **_opts) { str.upcase }
})

Defined Under Namespace

Classes: Parser, Resolver, Scope

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(locale, functions: {}, use_isolating: true, transform: nil) ⇒ Bundle

Create a new Bundle instance.

Examples:

Basic bundle creation

locale = ICU4X::Locale.parse("en-US")
bundle = Foxtail::Bundle.new(locale)

Parameters:

  • locale (ICU4X::Locale)

    The locale for this bundle

  • functions (Hash{String => #call}) (defaults to: {})

    Custom formatting functions (defaults to NUMBER and DATETIME)

  • use_isolating (Boolean) (defaults to: true)

    Whether to use Unicode bidi isolation marks for placeables (default: true)

  • transform (#call, nil) (defaults to: nil)

    Optional message transformation function (not currently implemented)

Raises:

  • (ArgumentError)

    if locale is not an ICU4X::Locale instance



52
53
54
55
56
57
58
59
60
61
# File 'lib/foxtail/bundle.rb', line 52

def initialize(locale, functions: {}, use_isolating: true, transform: nil)
  raise ArgumentError, "locale must be an ICU4X::Locale instance, got: #{locale.class}" unless locale.is_a?(ICU4X::Locale)

  @locale = locale
  @messages = {}  # id → Bundle::Parser::AST Message
  @terms = {}     # id → Bundle::Parser::AST Term
  @functions = Function.defaults.merge(functions).freeze
  @use_isolating = use_isolating
  @transform = transform
end

Instance Attribute Details

#functionsHash{String => #call} (readonly)

Returns Custom formatting functions.

Returns:

  • (Hash{String => #call})

    Custom formatting functions



37
38
39
# File 'lib/foxtail/bundle.rb', line 37

def functions
  @functions
end

#localeICU4X::Locale (readonly)

Returns The locale for this bundle.

Returns:

  • (ICU4X::Locale)

    The locale for this bundle



31
32
33
# File 'lib/foxtail/bundle.rb', line 31

def locale
  @locale
end

#messagesHash{String => Bundle::Parser::AST::Message} (readonly)

Returns Message entries indexed by ID.

Returns:



33
34
35
# File 'lib/foxtail/bundle.rb', line 33

def messages
  @messages
end

#termsHash{String => Bundle::Parser::AST::Term} (readonly)

Returns Term entries indexed by ID.

Returns:



35
36
37
# File 'lib/foxtail/bundle.rb', line 35

def terms
  @terms
end

#transform#call? (readonly)

Returns Optional message transformation function.

Returns:

  • (#call, nil)

    Optional message transformation function



39
40
41
# File 'lib/foxtail/bundle.rb', line 39

def transform
  @transform
end

Instance Method Details

#add_resource(resource, allow_overrides: false) ⇒ self

Add a resource to this bundle

Parameters:

  • resource (Resource)

    The resource to add

  • allow_overrides (Boolean) (defaults to: false)

    Whether to allow overriding existing messages/terms

Returns:

  • (self)

    Returns self for method chaining



71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/foxtail/bundle.rb', line 71

def add_resource(resource, allow_overrides: false)
  resource.entries.each do |entry|
    # In fluent-bundle format, terms have '-' prefix in id
    if entry.id&.start_with?("-")
      add_term_entry(entry, allow_overrides)
    else
      add_message_entry(entry, allow_overrides)
    end
  end

  self
end

#format(id, errors = nil) ⇒ String

Format a message with the given arguments. Keyword arguments are substituted into the message as variables.

Examples:

Basic message formatting

bundle.format("hello", name: "Alice")
# => "Hello, Alice!" (assuming message: hello = Hello, {$name}!)

Pluralization

bundle.format("emails", count: 1)
# => "You have one email." (assuming plural message)

With error collection

errors = []
bundle.format("hello", errors, name: "Alice")
# errors will contain any resolution errors

Parameters:

  • id (String, Symbol)

    Message identifier to format

  • errors (Array, nil) (defaults to: nil)

    If provided, errors are collected into this array instead of being ignored.

Returns:

  • (String)

    Formatted message string, or the id itself if message not found



119
120
121
122
123
124
# File 'lib/foxtail/bundle.rb', line 119

def format(id, errors=nil, **)
  message = message(id)
  return id.to_s unless message

  format_pattern(message.value, errors, **)
end

#format_pattern(pattern, errors = nil) ⇒ String

Format a pattern with the given arguments (using Resolver)

Parameters:

  • pattern (String, Array)

    The pattern to format

  • errors (Array, nil) (defaults to: nil)

    If provided, errors are collected into this array

Returns:

  • (String)

    Formatted result



131
132
133
134
135
136
137
138
139
140
# File 'lib/foxtail/bundle.rb', line 131

def format_pattern(pattern, errors=nil, **)
  scope = Scope.new(self, **)
  resolver = Resolver.new(self)
  result = resolver.resolve_pattern(pattern, scope)

  # Copy errors to provided array if given
  errors&.concat(scope.errors)

  result
end

#message(id) ⇒ Bundle::Parser::AST::Message?

Get a message by ID



90
# File 'lib/foxtail/bundle.rb', line 90

def message(id) = @messages[id.to_s]

#message?(id) ⇒ Boolean

Check if a message exists

Returns:

  • (Boolean)


86
# File 'lib/foxtail/bundle.rb', line 86

def message?(id) = @messages.key?(id.to_s)

#term(id) ⇒ Bundle::Parser::AST::Term?

Get a term by ID (private method in fluent-bundle)

Returns:



98
# File 'lib/foxtail/bundle.rb', line 98

def term(id) = @terms[id.to_s]

#term?(id) ⇒ Boolean

Check if a term exists (private method in fluent-bundle)

Returns:

  • (Boolean)


94
# File 'lib/foxtail/bundle.rb', line 94

def term?(id) = @terms.key?(id.to_s)

#use_isolating?Boolean

Returns Whether to use Unicode bidi isolation marks.

Returns:

  • (Boolean)

    Whether to use Unicode bidi isolation marks



64
# File 'lib/foxtail/bundle.rb', line 64

def use_isolating? = @use_isolating