02 - Up & Running
Mix.install([
:gettext,
:ex_cldr,
:ex_cldr_numbers,
:ex_cldr_dates_times,
:ex_cldr_trans,
:jason
])
# find the current directory, where the livebook is running
{current_dir, _} = System.cmd("pwd", [])
# create new directories for translation files
System.cmd("mkdir", ["-p", "priv/gettext/hi_IN/LC_MESSAGES"])
System.cmd("mkdir", ["-p", "priv/gettext/pt_BR/LC_MESSAGES"])
# create a new ".po" translation file
sample_po_translation_file_hi = """
msgid "%{count} reviews"
msgstr "%{count} समीक्षाएँ"
msgid "Color"
msgstr "रंग"
msgid "Size"
msgstr "आकार"
msgid "Size guide"
msgstr "साइज़ संदर्शिका"
"""
sample_po_translation_file_pt = """
msgid "%{count} reviews"
msgstr "%{count} avaliações"
msgid "Color"
msgstr "Cor"
msgid "Size"
msgstr "Tamanho"
msgid "Size guide"
msgstr "Guia de tamanho"
"""
{:ok, file} = File.open("priv/gettext/hi_IN/LC_MESSAGES/default.po", [:write, :utf8])
IO.write(file, sample_po_translation_file_hi)
{:ok, file} = File.open("priv/gettext/pt_BR/LC_MESSAGES/default.po", [:write, :utf8])
IO.write(file, sample_po_translation_file_pt)
# define a gettext backend module
defmodule DemoApp.Gettext do
use Gettext, otp_app: :foo
end
# define a CLDR backend module
defmodule DemoApp.Backend do
use Cldr,
locales: ["en", "de", "pt", "hi"],
default_locale: "en",
providers: [Cldr.Number, Cldr.Calendar, Cldr.DateTime, Cldr.List, Cldr.Trans],
json_library: Jason
end
# Set an app-wide default backend
Application.put_env(:ex_cldr, :default_backend, DemoApp.Backend)
Translating static strings with gettext
# the 'locale' of the current Elixir process can be set explicitly
Gettext.put_locale("pt_BR")
Gettext.get_locale()
"pt_BR"
# import your app's gettext 'backend' module to have the markup functions available
import DemoApp.Gettext
DemoApp.Gettext
# static strings that are marked-up using gettext, can be translated at runtime
gettext("Color")
|> IO.inspect()
gettext("Size")
|> IO.inspect()
gettext("Size guide")
|> IO.inspect()
:ok
"Cor"
"Tamanho"
"Guia de tamanho"
:ok
# if a translation is not available, the original string in the 'default' language is returned
gettext("This has not been translated yet.")
"This has not been translated yet."
# variables can be interpolated dynamically
num_reviews = 117
gettext("%{count} reviews", count: num_reviews)
"117 avaliações"
# we can wrap code our code to set a different locale explicitly
Gettext.put_locale("pt_BR")
Gettext.with_locale("hi_IN", fn ->
gettext("%{count} reviews", count: num_reviews)
|> IO.inspect(label: :hi_IN)
end)
gettext("%{count} reviews", count: num_reviews)
|> IO.inspect(label: :pt_BR)
:ok
hi_IN: "117 समीक्षाएँ"
pt_BR: "117 avaliações"
:ok
Handling variables with ex_cldr
Cldr.Number.to_string!(1_000_000, locale: "en_US")
|> IO.inspect()
Cldr.Number.to_string!(1_000_000, locale: "pt-BR")
|> IO.inspect()
Cldr.Number.to_string!(1_000_000, locale: "hi-IN")
|> IO.inspect()
:ok
"1,000,000"
"1.000.000"
"10,00,000"
:ok
Cldr.Number.System.to_system!(123, :roman, DemoApp.Backend)
|> IO.inspect()
Cldr.Number.System.to_system!(123, :thai, DemoApp.Backend)
|> IO.inspect()
Cldr.Number.System.to_system!(123, :arab, DemoApp.Backend)
|> IO.inspect()
Cldr.Number.System.to_system!(123, :deva, DemoApp.Backend)
|> IO.inspect()
:ok
"CXXIII"
09:59:05.544 [warning] Transliteration from number system :latn to :thai requires dynamic generation of a transliteration map for each function call which is slow. Please consider configuring this transliteration pair. See `Cldr.Number.Transliteration` for further information.
"๑๒๓"
09:59:05.544 [warning] Transliteration from number system :latn to :arab requires dynamic generation of a transliteration map for each function call which is slow. Please consider configuring this transliteration pair. See `Cldr.Number.Transliteration` for further information.
"١٢٣"
09:59:05.544 [warning] Transliteration from number system :latn to :deva requires dynamic generation of a transliteration map for each function call which is slow. Please consider configuring this transliteration pair. See `Cldr.Number.Transliteration` for further information.
"१२३"
:ok
Cldr.Number.to_string!(59.95, locale: "hi-IN", currency: "USD")
|> IO.inspect()
Cldr.Number.to_string!(59.95, locale: "pt-BR", currency: "USD")
|> IO.inspect()
Cldr.Number.to_string!(59.95, locale: "de-DE", currency: "USD")
|> IO.inspect()
:ok
"$59.95"
"US$ 59,95"
"59,95 $"
:ok
Cldr.Date.to_string!(~D[1985-12-21], format: :short, locale: "en-US")
|> IO.inspect()
Cldr.Date.to_string!(~D[1985-12-21], format: :short, locale: "hi-IN")
|> IO.inspect()
Cldr.Date.to_string!(~D[1985-12-21], format: :short, locale: "de-DE")
|> IO.inspect()
:ok
"12/21/85"
"21/12/85"
"21.12.85"
:ok
{:ok, calendar_us} = Cldr.Calendar.FiscalYear.calendar_for("US")
{:ok, calendar_jp} = Cldr.Calendar.FiscalYear.calendar_for("JP")
{:ok, calendar_in} = Cldr.Calendar.FiscalYear.calendar_for("IN")
Date.utc_today()
|> Cldr.Calendar.Interval.quarter()
|> IO.inspect()
Date.utc_today()
|> Date.convert!(Cldr.Calendar.NRF)
|> Cldr.Calendar.Interval.quarter()
|> IO.inspect()
Date.utc_today()
|> Date.convert!(Cldr.Calendar.FiscalYear.US)
|> Cldr.Calendar.Interval.quarter()
|> IO.inspect()
Date.range(~D[2023-07-01], ~D[2023-09-30])
Date.range(~D[2023-W27-1 Cldr.Calendar.NRF], ~D[2023-W39-7 Cldr.Calendar.NRF])
Date.range(~D[2023-10-01 Cldr.Calendar.FiscalYear.US], ~D[2023-12-30 Cldr.Calendar.FiscalYear.US])
Date.range(~D[2023-10-01 Cldr.Calendar.FiscalYear.US], ~D[2023-12-30 Cldr.Calendar.FiscalYear.US])
current_quarter_us_fiscal =
Date.utc_today()
|> Date.convert!(Cldr.Calendar.FiscalYear.US)
|> Cldr.Calendar.Interval.quarter()
Date.range(~D[2023-10-01 Cldr.Calendar.FiscalYear.US], ~D[2023-12-30 Cldr.Calendar.FiscalYear.US])
current_quarter_us_fiscal
|> Cldr.Calendar.Duration.new!()
|> Cldr.Calendar.Duration.to_string!()
{:ok, "2 months, 29 days"}
first_day =
current_quarter_us_fiscal.first()
|> Date.convert!(Calendar.ISO)
~D[2023-07-01]
last_day =
current_quarter_us_fiscal.last()
|> Date.convert!(Calendar.ISO)
~D[2023-09-30]
current_quarter_us_fiscal
|> Enum.map(fn date ->
Date.convert!(date, Calendar.ISO)
end)
[~D[2023-07-01], ~D[2023-07-02], ~D[2023-07-03], ~D[2023-07-04], ~D[2023-07-05], ~D[2023-07-06],
~D[2023-07-07], ~D[2023-07-08], ~D[2023-07-09], ~D[2023-07-10], ~D[2023-07-11], ~D[2023-07-12],
~D[2023-07-13], ~D[2023-07-14], ~D[2023-07-15], ~D[2023-07-16], ~D[2023-07-17], ~D[2023-07-18],
~D[2023-07-19], ~D[2023-07-20], ~D[2023-07-21], ~D[2023-07-22], ~D[2023-07-23], ~D[2023-07-24],
~D[2023-07-25], ~D[2023-07-26], ~D[2023-07-27], ~D[2023-07-28], ~D[2023-07-29], ~D[2023-07-30],
~D[2023-07-31], ~D[2023-08-01], ~D[2023-08-02], ~D[2023-08-03], ~D[2023-08-04], ~D[2023-08-05],
~D[2023-08-06], ~D[2023-08-07], ~D[2023-08-08], ~D[2023-08-09], ~D[2023-08-10], ~D[2023-08-11],
~D[2023-08-12], ~D[2023-08-13], ~D[2023-08-14], ~D[2023-08-15], ~D[2023-08-16], ~D[2023-08-17],
~D[2023-08-18], ~D[2023-08-19], ...]
# previous_quarter_us_fiscal
Cldr.Calendar.previous(current_quarter_us_fiscal, :quarter)
Date.range(~D[2023-07-01 Cldr.Calendar.FiscalYear.US], ~D[2023-09-30 Cldr.Calendar.FiscalYear.US])
Date.utc_today()
|> Date.convert!(Cldr.Calendar.FiscalYear.US)
|> Cldr.Calendar.localize(:quarter)
|> IO.inspect()
Date.utc_today()
|> Date.convert!(Cldr.Calendar.FiscalYear.JP)
|> Cldr.Calendar.localize(:quarter)
|> IO.inspect()
:ok
"Q4"
"Q2"
:ok
date =
Date.utc_today()
|> Date.convert!(Cldr.Calendar.FiscalYear.US)
Cldr.Calendar.Interval.quarter(date)
Date.range(~D[2023-10-01 Cldr.Calendar.FiscalYear.US], ~D[2023-12-30 Cldr.Calendar.FiscalYear.US])
{:ok, duration} = Cldr.Calendar.Duration.new(~D[2025-01-01], ~D[2025-12-31])
Cldr.Calendar.Duration.to_string!(duration)
"11 months, 30 days"
now = DateTime.now!("Etc/UTC")
earlier = DateTime.add(now, -300)
Cldr.DateTime.Relative.to_string!(earlier, relative_to: now, locale: "de-DE")
|> IO.inspect()
{:ok, duration} = Cldr.Calendar.Duration.new(~D[2025-01-01], ~D[2025-12-31])
Cldr.Calendar.Duration.to_string!(duration, locale: "de-DE")
|> IO.inspect()
{:ok, duration} = Cldr.Calendar.Duration.new(~D[2025-01-01], ~D[2025-12-31])
Cldr.Calendar.Duration.to_string!(duration, locale: "de-DE", backend: DemoApp.Backend)
|> IO.inspect()
"vor 5 Minuten"
"11 months, 30 days"
"11 months, 30 days"
"11 months, 30 days"
now = DateTime.now!("Etc/UTC")
earlier = DateTime.add(now, -300)
Cldr.DateTime.Relative.to_string!(earlier, relative_to: now, locale: "en-US")
|> IO.inspect()
Cldr.DateTime.Relative.to_string!(earlier, relative_to: now, locale: "de-DE")
|> IO.inspect()
Cldr.DateTime.Relative.to_string!(earlier, relative_to: now, locale: "pt_BR")
|> IO.inspect()
:ok
"5 minutes ago"
"vor 5 Minuten"
"há 5 minutos"
:ok
Translating database records with ex_cldr_trans
defmodule DemoApp.Product do
use Ecto.Schema
use DemoApp.Backend.Trans, translates: [:title, :description]
schema "products" do
field(:title, :string)
field(:description, :string)
# use the 'translations' macro to set up a map-field with a set of nested
# structs to handle translation values for each configured locale and each
# translatable field
translations(:translations)
end
end
{:module, DemoApp.Product, <<70, 79, 82, 49, 0, 0, 20, ...>>,
[__schema__: 1, __schema__: 1, __schema__: 1, __schema__: 1, __schema__: 2, __schema__: 2, ...]}
DemoApp.Product.Translations.__schema__(:fields)
[:de, :hi, :pt]
product = %DemoApp.Product{
title: "Basic Tee 6-Pack",
description: "The Basic Tee 6-Pack allows you to fully express your vibrant personality ...",
translations: %DemoApp.Product.Translations{
pt: %DemoApp.Product.Translations.Fields{
title: "camiseta básica",
description:
"O Basic Tee 6-Pack permite que você expresse totalmente sua personalidade vibrante ..."
}
# de: ...,
# hi: ...
}
}
IO.inspect(product.title)
IO.inspect(product.description)
product_pt = Cldr.Trans.Translator.translate(product, :pt)
IO.inspect(product_pt.title)
IO.inspect(product_pt.description)
:ok
"Basic Tee 6-Pack"
"The Basic Tee 6-Pack allows you to fully express your vibrant personality ..."
"camiseta básica"
"O Basic Tee 6-Pack permite que você expresse totalmente sua personalidade vibrante ..."
:ok