Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

02 - Up & Running

02-up-and-running.livemd

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