A multiformat internationalization (i18n) shard for Crystal. Supports Gettext, Ruby YAML, etc gettext i18n l18n internationalization localization l10n translation locale language
0.1.0 Latest release released
8 1 3


A multiformat internationalization (i18n) shard for Crystal.

There is no doubt that the crystal community lacks many essential features that most modern day languages have; one of which is internationalization. Lens is an attempt to fix that.

Getting started

Lens supports numerous different formats:

  • GNU Gettext (Gettext::POBackend, Gettext::MOBackend)
    • You can use both .po and .mo
  • Crystal-i18n (CrystalI18n::I18n)
    • Stored under .yml
  • More coming soon

To get started, you should select one that fits your needs the best. See here for information regarding them.

Each backend can then be initialized with the locale directory

backend = Gettext::MOBackend.new("locales")

Each backend also supports nested directories and multiple files of the same language.

GNU Gettext

The GNU project's implementation of Gettext

backend = Gettext::MOBackend.new("locales")
catalogue_hash = backend.create() # => LanguageCode|| Filename => Catalogue 
catalogue = catalogue_hash["en_US"]

For detailed information regarding usage please refer to the actual API documentation. A brief overview of the functionality is present below.

Basic usage

Messages are fetched with the gettext method. If it doesn't exist then the given ID would be returned.

catalogue.gettext("A message")     # => "Translated message"
catalogue.gettext("I don't exist") # => "I don't exist"


Pluralization is done through the ngettext method. The given number is parsed through the C expression from the Plural-Forms header to know which plural-form to use.

catalogue.ngettext("I have %d apple", "I have %d apples", 0) # => "Translated I have %d apple"
catalogue.ngettext("I have %d apple", "I have %d apples", 1) # => "Translated I have %d apples"


Key can be constrained by a specific context and is accessed through the pgettext method.

catalogue.pgettext("CopyPasteMenu", "copy")          # => "Translated copy"
catalogue.pgettext("CopyPasteMenu", "I don't exist") # => "I don't exist"

Context with pluralization

Context-constrained messages can also have plural-forms and is accessed through the npgettext method

catalogue.npgettext("CopyPasteMenu", "Export %d file", "Export %d files", 0) # => "Translated message with plural-form 1"
catalogue.npgettext("CopyPasteMenu", "Export %d file", "Export %d files", 1) # => "Translated message with plural-form 0"


Is a format based on ruby-yml similar to what many others in the crystal community has implemented.

# The backend is the catalogue in the case of crystal-i18n
catalogue = CrystalI18n::I18n.new("locales")

Each file should be named like language-code.yml

For detailed information regarding usage please refer to the actual API documentation. A brief overview of the functionality is present below.

Basic usage

catalogue.translate("en", "translation") # => "Translated Message"

Nested keys can be accessed by separating routes with .

catalogue.translate("en", "nested_key.forth.forth-third.forth-third-fifth.4344")


catalogue.translate("en", "possessions.fruits.apples", 50) # => "I have 50 apples"
catalogue.translate("en", "possessions.fruits.apples", 1)  # => "I have 1 apple"

Plural-rules follows CLDR and is pre-defined in lens for many languages. If your language isn't included in lens you many define (or even overwrite) a new plural-rule through the CrystalI18n.define_rule method

  CrystalI18n.define_rule("ar", ->(n : Int32 | Int64 | Float64) {
  when n == 0             then "zero"
  when n == 1             then "one"
  when n == 2             then "two"
  when 3..10 === n % 100  then "few"
  when 11..99 === n % 100 then "many"
  else                         "other"


Interpolation is done through keyword arguments in the #translate method

# message is 'Hello there, my name is %{name} and I'm a %{profession}`.
result = catalogue.translate("en", "introduction.messages", name: "Steve", profession: "programmer")
result # => "Hello there, my name is Steve and I'm a programmer"


If the value at the given path (key) turns out to be an array then you can pass in the iter argument to select a specific value at the given index

catalogue.translate("en", "items.foods", iter: 2) # => "Hamburger"

API documentation



  1. Add the dependency to your shard.yml:

        github: syeopite/lens
  2. Run shards install


  1. Fork it (https://github.com/syeopite/lens/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request



  github: syeopite/lens
  version: ~> 0.1.0
License MIT
Crystal >= 1.0, < 2.0


Dependencies 0

Development Dependencies 0

Dependents 0

Last synced .
search fire star recently