Add this to your application's shard.yml
:
dependencies:
i18n:
github: crimson-knight/i18n.cr
I18n.translate(
"some.dot.separated.path", # key : String
{attr_to_interpolate: "a"}, # options : Hash | NamedTuple? = nil
"pt", # force_locale : String = nil
2, # count : Numeric? = nil
"default translation", # default : String? = nil
nil # iter : Int? = nil
)
I18n.localize(
Time.utc_now, # object : _
"pt", # force_locale : String = I18n.config.locale
:time, # scope : Symbol? = :number
"long" # format : String? = nil
)
Translation may include argument interpolation. For doing this use regular crystal named interpolation placeholder and pass hash or named tuple as a options
argument:
message:
new: "New message: %{text}"
# New message: hello
I18n.translate("message.new", {text: "hello"})
I18n.translate("message.new", {:text => "hello"})
I18n.translate("message.new", {"text" => "hello"})
Also any extra key-value pair will be ignored and missing one will not cause any exception:
I18n.translate("message.new", {message: "hello"}) # New message: %{text}
require "i18n"
I18n.load_path += ["spec/locales"]
I18n.init # This will load locales from all specified locations
I18n.default_locale = "pt" # default can be set after loading translations
There is a handler for Kemalyst that bring I18n configuration.
A simple backend that reads translations from YAML files and stores them in an in-memory hash. Also supports JSON files and translations embedding.
Backend that chains multiple other backends and checks each of them when a translation needs to be looked up. This is useful when you want to use standard translations with a custom backend to store custom application translations in a database or other backends.
To use the Chain backend instantiate it and set it to the I18n module. You can add chained backends through the initializer:
require "i18n/backend/chain"
other_backend = I18n::Backend::Yaml.new # your other backend
I18n.backend.load("config/locales/en.yml")
other_backend.load("config/locales/pl.yml")
I18n.backend = I18n::Backend::Chain.new([I18n.backend, other_backend2] of I18n::Backend::Base)
# or if it is ok to pass files to each backend
I18n.backend = I18n::Backend::Chain.new([I18n.backend, I18n::Backend::Yaml.new] of I18n::Backend::Base)
I18n.load_path = ["config/locales/{en,pl}.yml"]
I18n.load
I18n locale fallbacks are useful when you want your application to use translations from other locales when translations for the current locale are missing. E.g. you might want to use en
translations when translations in your applications main locale de
are missing.
To enable locale fallbacks you can instantiate fallback backend giving it your backend as an argument:
require "i18n/backend/fallback"
I18n.load_path = ["config/locales"]
I18n.init
I18n.backend = I18n::Backend::Fallback.new(I18n.backend, {"en-US" => "en", "en-UK" => "en"})
Putting translations for all parts of your application in one file per locale could be hard to manage. You can store these files in a hierarchy which makes sense to you.
For example, your config/locales directory could look like this:
locales
|--defaults
|----en.yml
|----pt.yml
|--models
|----en.yml
|----pt.yml
|--views
|----users
|------en.yml
|------pt.yml
This way you can separate model related translations from the view ones. To require all described subfolders at once use **
- I18n.load_path += ["locals/**/"]
Any .json
file located in the file hierarchy specified for load_path
is also read and parsed.
To localize the time (or date) format you should pass Time
object to the I18n.localize
. To pick a specific format path format
argument:
I18n.localize(Time.local, scope: :date, format: :long)
By default
Time
will be localized with:time
scope.
To specify formats and all need localization information (like day or month names) fill your file in following way:
NOTE: According to ISO 8601, Monday is the first day of the week
__formats__:
date:
formats:
default: '%Y-%m-%d' # is used by default
long: '%A, %d de %B %Y'
month_names: # long month names
- Janeiro
- Fevereiro
- Março
- Abril
- Maio
- Junho
- Julho
- Agosto
- Setembro
- Outubro
- Novembro
- Dezembro
abbr_month_names: # month abbreviations
- Jan
- Fev
# ...
day_names: # fool day names
- Segunda
# ...
abbr_day_names: # short day names
- Seg
# ...
Format accepts any crystal Time::Format
directives. Also following directives will be automatically localized:
Directive | Description | Key |
---|---|---|
%a |
short day name | date.abbr_day_names |
%A |
day name | date.day_names |
%b |
short month name | date.abbr_month_names |
%B |
month name | date.month_names |
%p |
am-pm (lowercase) | time.am /time.pm |
%P |
AM-PM (uppercase) | time.am /time.pm |
In many languages — including English — there are only two forms, a singular and a plural, for a given string, e.g. "1 message" and "2 messages". Other languages (Arabic, Japanese, Russian and many more) have different grammars that have additional or fewer plural forms.
The count
interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR:
message:
one: "%{count} message"
other: "%{count} messages"
I18n.translate("message", count: 1) # 1 message
I18n.translate("message", count: 2) # 2 messages
I18n.translate("message", count: 0) # 0 messages
count
should be passed as argument - not inside ofoptions
. Otherwise regular translation lookup will be applied.
I18n defines default CLDR rules for many locales (see src/i18n/config/plural_rules
), however they can be overwritten:
I18n.plural_rules["ru"] = ->(n : Int32) {
if n == 0
:zero
elsif ((n % 10) == 1) && ((n % 100 != 11))
# 1, 21, 31, 41, 51, 61...
:one
elsif ([2, 3, 4].includes?(n % 10) && ![12, 13, 14].includes?(n % 100))
# 2-4, 22-24, 32-34...
:few
elsif ((n % 10) == 0 || ![5, 6, 7, 8, 9].includes?(n % 10) || ![11, 12, 13, 14].includes?(n % 100))
# 0, 5-20, 25-30, 35-40...
:many
else
:other
end
}
kid:
zero: 'нет детей'
one: '%{count} ребенок'
few: '%{count} ребенка'
many: '%{count} детей'
other: '%{count} детей'
I18n.locale = "ru"
I18n.translate("kid", count: 0) # нет детей
I18n.translate("kid", count: 1) # 1 ребенок
I18n.translate("kid", count: 2) # 2 ребенка
I18n.translate("kid", count: 6) # 6 детей
To store several alternative objects under one localization key they could be just listed in the file and later retrieved using iter
argument:
NOTE : The first index is
0
__formats__:
date:
day_names: [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]
I18n.translate("__formats__.date.day_names", iter: 2) # >>> "Wednesday"
- Supported Crystal versions of >= 0.35
- add Backend::Yaml#exists? to check whether given translation key exists
- add I18n.exists?
- add docs to I18n module public methods (most of wording was taken from the ruby I18n repo)
- Fixed the iter argument in
translate
to properly return the correct index
Example
# Array we are looking into ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
I18n.translate("__formats__.date.day_names", iter: 2) # Returns "Wednesday"
- Pluralization rules are now fully suites CLDR convention. Specifically
en
pluralization no more returnszero
- The first day of the week is now Monday according to ISO 8601.
- The nil value in
month_names
andabbr_month_names
was removed.
You can embed translations inside your binary by using the following macro call:
I18n::Backend::Yaml.embed(["some/locale/directory", "some/other/locale/directory"])
- Fork it ( https://github.com/crimson-knight/i18n/fork )
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create a new Pull Request
- [crimson-knight] Seth Tucker (New owner as of July 2021)
- [imdrasil] Roman Kalnytskyi
Inspiration taken from:
Special thank you to [TechMagister] for being the original owner and creator of this shard.