Skip to content

Commit

Permalink
Merge pull request #240 from pulibrary/i123-localization
Browse files Browse the repository at this point in the history
Adds localization features
  • Loading branch information
hackartisan authored Nov 22, 2024
2 parents 10c091b + 5ca336d commit f157ac1
Show file tree
Hide file tree
Showing 18 changed files with 807 additions and 32 deletions.
4 changes: 4 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ config :phoenix, :json_library, Jason

config :dpul_collections, :figgy_hydrator, poll_interval: 60000

config :dpul_collections, DpulCollectionsWeb.Gettext,
default_locale: "en",
locales: ~w(en es)

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"
38 changes: 38 additions & 0 deletions docs/locale/adding_translations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Localization (aka, Internationalization)

## How to Add Localized Translations

1. Any strings that require translation should be added using `gettext("Hello")`
2. Generate translation templates containing placeholders for all strings wrapped with gettext() by running `mix gettext.extract` to extract gettext() calls to .pot (Portable Object Template) files, which are the base for all translations.
3. In the console, run `mix gettext.merge priv/gettext` to update all locale-specific .po (Portable Object) files so that they include this message ID. Entries in PO files contain translations for their specific locale.
4. Add translations into the non-English .po files found in `priv/gettext/`. For example, to add the Spanish translations, update the `msgstr` for each English word you want to translate.

## How to Edit Localized Translations
If you edit the original string ids, simply run mix gettext.extract again. Then, the next mix gettext.merge can do fuzzy matching. So, if you change "Hello world" to "Hello world!", Gettext will see that the new message ID is similar to an existing msgid, and will do two things:

1. It will update the msgid in all .po files to match the new text.

2. It will mark those entries as "fuzzy"; this hints that a (probably human) translator should check whether the Italian translation of this string needs an update.

_see https://hexdocs.pm/gettext/Gettext.html for additional details_

## Special Cases: LUX Components
There are a couple of "gotchas" to be aware of for any LUX components that require translations. LUX components used include the Header and Footer elements.
_see: lib/dpul_collections_web/components/lux-components_

### Use Longform for Vue.js Bindings
Heex templates will error when binding attributes using shorthand that starts with a ":".

Don't do this:
`<lux-menu-bar :menu-items="..."/>`

Do this:
`<lux-menu-bar v-bind:menu-items="..."/>`

### When using gettext inside of a component attribute
Use curly braces to turn the entire attribute value into a string and do Elixir-style string interpolation:

```
<lux-menu-bar v-bind:menu-items={"[{name: '#{gettext("Language")}'}]"} />
```

3 changes: 3 additions & 0 deletions lib/dpul_collections_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ defmodule DpulCollectionsWeb do
use Phoenix.LiveView,
layout: {DpulCollectionsWeb.Layouts, :app}

alias DpulCollectionsWeb.SetLocaleHook

on_mount SetLocaleHook
unquote(html_helpers())
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/dpul_collections_web/components/layouts/root.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
</head>
<body class="bg-white antialiased">
<div class="flex flex-col min-h-screen" id="app">
<%= raw(DpulCollectionsWeb.LuxComponents.header()) %>
<%= DpulCollectionsWeb.LuxComponents.header(assigns) %>
<div class="flex-1">
<%= @inner_content %>
</div>
Expand Down
22 changes: 16 additions & 6 deletions lib/dpul_collections_web/components/lux-components.ex
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
defmodule DpulCollectionsWeb.LuxComponents do
use Phoenix.Component
import DpulCollectionsWeb.Gettext

def header() do
~S"""
<lux-library-header app-name="Digital Collections" abbr-name="Collections" app-url="/" theme="shade">
<lux-menu-bar type="main-menu" :menu-items="[
{name: 'Language', component: 'Language', children: [
def header(assigns) do
~H"""
<lux-library-header
app-name="Digital Collections"
abbr-name="Collections"
app-url="/"
theme="shade"
>
<lux-menu-bar
type="main-menu"
v-bind:menu-items={"[
{name: '#{gettext("Language")}', component: 'Language', children: [
{name: 'English', component: 'English', href: '?locale=en'},
{name: 'Español', component: 'Español', href: '?locale=es'},
{name: 'Português do Brasil', component: 'Português do Brasil', href: '?locale=pt-BR'}
]}
]" theme="shade"/>
]"}
theme="shade"
/>
</lux-library-header>
"""
end
Expand Down
9 changes: 6 additions & 3 deletions lib/dpul_collections_web/live/home_live.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule DpulCollectionsWeb.HomeLive do
use DpulCollectionsWeb, :live_view
import DpulCollectionsWeb.Gettext
alias DpulCollections.Solr
alias DpulCollectionsWeb.Live.Helpers

Expand All @@ -21,15 +22,17 @@ defmodule DpulCollectionsWeb.HomeLive do
<div class="grid grid-cols-4">
<input class="col-span-4 md:col-span-3" type="text" name="q" value={@q} />
<button class="col-span-4 md:col-span-1 btn-primary" type="submit">
Search
<%= gettext("Search") %>
</button>
</div>
</form>
</div>
<div id="welcome" class="grid place-self-center gap-10 max-w-prose">
<h3 class="text-5xl text-center">Explore Our Digital Collections</h3>
<h3 class="text-5xl text-center"><%= gettext("Explore Our Digital Collections") %></h3>
<p class="text-xl text-center">
We invite you to be inspired by our globally diverse collections of <%= @item_count %> Ephemera items. We can't wait to see how you use these materials to support your unique research.
<%= gettext("We invite you to be inspired by our globally diverse collections of") %> <%= @item_count %> <%= gettext(
"Ephemera items. We can't wait to see how you use these materials to support your unique research."
) %>
</p>
</div>
</div>
Expand Down
21 changes: 11 additions & 10 deletions lib/dpul_collections_web/live/item_live.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule DpulCollectionsWeb.ItemLive do
use DpulCollectionsWeb, :live_view
import DpulCollectionsWeb.Gettext
alias DpulCollections.{Item, Solr}

def mount(_params, _session, socket) do
Expand All @@ -24,17 +25,11 @@ defmodule DpulCollectionsWeb.ItemLive do
def render(assigns) when is_nil(assigns.item) do
~H"""
<div class="my-5 grid grid-flow-row auto-rows-max gap-10">
<span>Item not found</span>
<span><%= gettext("Item not found") %></span>
</div>
"""
end

def description(assigns) do
~H"""
<div class="pb-4 leading-relaxed text-lg"><%= @description %></div>
"""
end

def render(assigns) do
~H"""
<div class="my-5 grid grid-flow-row auto-rows-max md:grid-cols-5 gap-4">
Expand All @@ -56,14 +51,14 @@ defmodule DpulCollectionsWeb.ItemLive do
height="800"
/>
<button class="w-full btn-primary">
Download
<%= gettext("Download") %>
</button>
</div>
<div class="md:hidden block">
<.description :for={description <- @item.description} description={description} />
</div>
<section class="md:col-span-5 m:order-last py-4">
<h2 class="text-xl font-bold py-4">Pages (<%= @item.page_count %>)</h2>
<h2 class="text-xl font-bold py-4"><%= gettext("Pages") %> (<%= @item.page_count %>)</h2>
<div class="flex flex-wrap gap-5 justify-center md:justify-start">
<.thumbs
:for={{thumb, thumb_num} <- Enum.with_index(@item.image_service_urls)}
Expand All @@ -77,6 +72,12 @@ defmodule DpulCollectionsWeb.ItemLive do
"""
end

def description(assigns) do
~H"""
<div class="pb-4 leading-relaxed text-lg"><%= @description %></div>
"""
end

def thumbs(assigns) do
~H"""
<div>
Expand All @@ -91,7 +92,7 @@ defmodule DpulCollectionsWeb.ItemLive do
/>
<button class="w-[350px] md:w-[225px] btn-primary">
<a href={"#{@thumb}/full/full/0/default.jpg"} target="_blank">
Download
<%= gettext("Download") %>
</a>
</button>
</div>
Expand Down
23 changes: 12 additions & 11 deletions lib/dpul_collections_web/live/search_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ end
defmodule DpulCollectionsWeb.SearchLive do
use DpulCollectionsWeb, :live_view
import DpulCollectionsWeb.SearchComponents
import DpulCollectionsWeb.Gettext
alias DpulCollections.{Item, Solr}
alias DpulCollectionsWeb.Live.Helpers

Expand Down Expand Up @@ -84,13 +85,13 @@ defmodule DpulCollectionsWeb.SearchLive do

def render(assigns) do
~H"""
<h1 class="sr-only">Search Results</h1>
<h1 class="sr-only"><%= gettext("Search Results") %></h1>
<div class="my-5 grid grid-flow-row auto-rows-max gap-10">
<form id="search-form" phx-submit="search">
<div class="grid grid-cols-4">
<input class="col-span-4 md:col-span-3" type="text" name="q" value={@search_state.q} />
<button class="col-span-4 md:col-span-1 btn-primary" type="submit">
Search
<%= gettext("Search") %>
</button>
</div>
</form>
Expand All @@ -101,31 +102,31 @@ defmodule DpulCollectionsWeb.SearchLive do
class="grid md:grid-cols-[150px_200px_200px_200px] gap-2"
>
<label class="col-span-1 self-center font-bold uppercase" for="date-filter">
filter by date:
<%= gettext("filter by date") %>:
</label>
<input
class="col-span-1"
type="text"
placeholder="From"
placeholder={gettext("From")}
form="date-filter"
name="date-from"
value={@search_state.date_from}
/>
<input
class="col-span-1"
type="text"
placeholder="To"
placeholder={gettext("To")}
form="date-filter"
name="date-to"
value={@search_state.date_to}
/>
<button class="col-span-1 md:col-span-1 btn-primary" type="submit">
Apply
<%= gettext("Apply") %>
</button>
</form>
<form id="sort-form" class="grid md:grid-cols-[auto_200px] gap-2" phx-change="sort">
<label class="col-span-1 self-center font-bold uppercase md:text-right" for="sort-by">
sort by:
<%= gettext("sort by") %>:
</label>
<select class="col-span-1" name="sort-by">
<%= Phoenix.HTML.Form.options_for_select(
Expand Down Expand Up @@ -166,7 +167,7 @@ defmodule DpulCollectionsWeb.SearchLive do
thumb_num={thumb_num}
/>
<div :if={@item.page_count > 1} class="absolute right-0 top-0 bg-white px-4 py-2">
<%= @item.page_count %> Pages
<%= @item.page_count %> <%= gettext("Pages") %>
</div>
</div>
<h2 class="underline text-2xl font-bold pt-4">
Expand Down Expand Up @@ -201,7 +202,7 @@ defmodule DpulCollectionsWeb.SearchLive do
phx-click="paginate"
phx-value-page={@page - 1}
>
<span class="sr-only">Previous</span>
<span class="sr-only"><%= gettext("Previous") %></span>
<svg
class="w-2.5 h-2.5 rtl:rotate-180"
aria-hidden="true"
Expand Down Expand Up @@ -244,7 +245,7 @@ defmodule DpulCollectionsWeb.SearchLive do
phx-click="paginate"
phx-value-page={@page + 1}
>
<span class="sr-only">Next</span>
<span class="sr-only"><%= gettext("Next") %></span>
<svg
class="w-2.5 h-2.5 rtl:rotate-180"
aria-hidden="true"
Expand Down Expand Up @@ -303,7 +304,7 @@ defmodule DpulCollectionsWeb.SearchLive do

def handle_event("paginate", %{"page" => page}, socket) do
params = %{socket.assigns.search_state | page: page} |> Helpers.clean_params()
socket = push_redirect(socket, to: ~p"/search?#{params}")
socket = push_navigate(socket, to: ~p"/search?#{params}")
{:noreply, socket}
end

Expand Down
1 change: 1 addition & 0 deletions lib/dpul_collections_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule DpulCollectionsWeb.Router do
plug :put_root_layout, html: {DpulCollectionsWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug DpulCollectionsWeb.LocalePlug, backend: DpulCollectionsWeb.Gettext
end

pipeline :dashboard_auth do
Expand Down
19 changes: 19 additions & 0 deletions lib/dpul_collections_web/set_locale_hook.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule DpulCollectionsWeb.SetLocaleHook do
@moduledoc """
Tells Gettext which locale to use for rendering LiveViews on the front-end.
Locale should be set in the session in lib/dpul_collections_web/plug/locale_plug.ex
If no locale is in the session it defaults to English (en).
"""

def on_mount(:default, _params, %{"locale" => locale}, socket) do
Gettext.put_locale(DpulCollectionsWeb.Gettext, locale)
{:cont, Phoenix.Component.assign(socket, :locale, locale)}
end

def on_mount(:default, _params, _session, socket) do
# Fallback when "locale" is not in session
default_locale = "en"
Gettext.put_locale(DpulCollectionsWeb.Gettext, default_locale)
{:cont, Phoenix.Component.assign(socket, :locale, default_locale)}
end
end
Loading

0 comments on commit f157ac1

Please sign in to comment.