hwuethrich

hwuethrich

Hi!

I’m happy to share my first Elixir library. But first some …

Background

As an Elixir company from a country with 4 official languages (:switzerland:), trust me when I say: we are using Gettext a lot in our Phoenix projects.

I really love the features of Gettext, but it always bothered me that it adds a lot of noise in the code, especially when using domains/contexts, interpolations or pluralization (besides, I still can’t remember which Gettext macro to use when using domains and/or contexts :sweat_smile:).

That’s why until now, we always added a ~t sigil to all our (phoenix) projects that simply delegates to gettext. We also had an m modifier that is using the current module name (eg. live view/component) as context.

Over the last few days, I extracted this (and more!) as a library called …

gettext_sigils

Github / hex.pm

It provides a new sigil ~t (which felt oddly familiar) for using Gettext translations with less boilerplate and better readability:

# before
gettext("Hello, %{name}", name: user.name)

# after
~t"Hello, #{user.name}"

When using GettextSigils (eg. in your MyAppWeb.html_helpers/0 for Phoenix projects), you can also provide how modifiers are mapped do domains and/or contexts:

# replace this
use Gettext, backend: MyApp.Gettext

# with this
use GettextSigils, 
  backend: MyApp.Gettext
  sigils: [ 
    modifiers: [ 
      m: [context: inspect(__MODULE__)],
      e: [domain: "errors"]
    ]
  ]

# then use it instead of gettext

~t"This is a global message"
~t"This is scoped to the current module/view/component"m
~t"This is a scoped error message"em

If this sounds interesting, there are a few other features, all described in the README.

As this is a very new project, contributions, bug reports and feedback in general are very welcome. I’m currently working on adding pluralization (which is a bit tricky when all you have is a sigil) where I would love some feedback (PR).

Thanks! Danke! Merci! Grazie! Grazia! :heart:

Showing Posts 1 to 10

ErikNaslund

ErikNaslund

Thanks a bunch for this! I’ve also always been bothered by the verbosity of having gettext() calls all over my .heex templates, but I’ve never done anything about it.

I just migrated one of my projects to using GettextSigils, and one quirky regex replace and some small amount of manual editing later I was all done and it was working like a charm :slight_smile:

<.socialproof name={~t"Papa John"} title={~t"City Planner"}>
# ...is just so much nicer than....
<.socialproof name={gettext("Papa John")} title={gettext("City Planner")}>

Great “quality of life” improvement, so thanks again! :slight_smile:

P.S.
A serendipitous side effect is that my syntax highlighting scheme highlights sigil strings differently than normal binary strings, so it became a lot easier to spot “language strings” vs other strings (like CSS classes etc) in the templates as well.

josevalim

josevalim

Creator of Elixir

Love the idea!

12
Post #2
hwuethrich

hwuethrich OP

Thank you so much for the feedback! :smiley: I’m glad to know I wasn’t the only one bothered by the additional noise.

Here are some ideas I’d like to add until 1.x:

  • Pluralization - As I said, this is bit tricky because I think the only way is using a character or string to split the sigil. The existing PR is using (UTF-8 double vertical bar), but it’s not typeable. I’m thinking of maybe using something like // would be better.
  • Dynamic @doc for sigil_t that includes a list of modifiers (and what they are mapped to) that is displayed by the editor when using the sigil.
  • Include usage_rules for using the library, and a skill to replace fixed strings with ~t in an app (in case gettext was not used from the start).
DaAnalyst

DaAnalyst

Nice. I already implemented something like this for internationalizing our project, but plain macros, e.g:

t( "Translate me")
t( "%{count} character(s) remaining", count: @count)
..

What I’ve found extremely useful (and so implemented it) was the ability to define domain at the module level and so that all the t/1, t/2, tn/3, tn/4macros can use it without having to specify it on case by case basis, e.g:

use MyApp.GettextHelpers, domain: "edit_post"
hwuethrich

hwuethrich OP

Thanks for the feedback!

This is already possible by specifying the sigils: [domain: "edit_post"] option when using the module.

Using plain macros definitely has some benefits (if the macro takes more the 1 string, eg. pluralization).

BartOtten

BartOtten

Lovely. Had a POC that extracted all text from Heex (no sigil needed) but had issues with Gettext, HTML and variables. Maybe you could have a try as you clearly have more experience with Gettext :slight_smile:

Except from splitting sentences on every variabele and Heex-tag, it worked miracles. Every text from the app became a Gettext translatable message :slight_smile:

Help tool to check the tokenizer steps: GitHub - BartOtten/eex_visualizer: Visualizes the compilation steps of (h)EEx · GitHub

hwuethrich

hwuethrich OP

I just released v0.2.1 of the library with the following features:

igniter install task

installiing the library now automatically replaces use Gettext with use GettextSigils in the project. this allows installing and configuring the library with:

mix igniter.install gettext_sigils

usage rules & skill

I added usage rules to teach LLMs to use ~t instead of fixed strings for newly generated code. also comes with a skill that will:

  • replace fixed strings with ~t in any user-facing part of the application (HEEx templates)

  • suggest using ex_cldr when showing dates, time, numbers, etc.

  • at the end of a task, asks to translate the new Gettext message for all languages used in the project (optional)

  • this works for newly generated code and existing parts of the project!

While this might be a bit controversial (my wife is actually working as a translator :sweat_smile:), this has been a real time-saver and localizing an existing app is now easier than ever! At least it should mark the generated translations as “fuzzy”. See skill source and LLM guide.

hwuethrich

hwuethrich OP

This sounds interesting and scary at the same time! :sweat_smile: I assume there are a lot of edge cases like translated DOM attributes, args for nested components, embedded JS?

wintermeyer

wintermeyer

I love the idea! I run into this problem in every single Phoenix project.

Only the pluralization needs to be solved in a clean way since that is a showstopper.

hwuethrich

hwuethrich OP

Thanks for the feedback! Yeah, finding a clean solution for pluralization is a bit difficult (and that’s why I haven’t merged this PR, yet)

For now, I think this is my favorite solution so far:

~t"One post|#{count} posts"N

~t"One post|#{count :: length(@posts)} posts"Nm

It uses

  • a separator that is typable on a keyboard (and rarely used)
  • a reserved (uppercase) modifier N to mark it as pluralization (separator can still be used in regular translations, omitting the separator in pluralizations will raise an error)

But I’m open to other ideas!

Where Next? Top

Trending in News & Updates Top

kip
I’m a bit excited to announce that Localize and friends are now at release 1.0. Even though it’s a 1.0 release, it stands on 8 years of w...
New
bartblast
After building Hologram and sharing updates across various places, I’ve realized there’s a lot happening that doesn’t always make it to t...
New
bartblast
Hologram v0.11 is out! Two headline features this release. First, Elixir regexes now run in the browser. They were server-only until now,...
New
nseaSeb
Just published search_ash 0.5.0 (with search_core 0.4.0) on Hex. What’s new: synonyms You can now declare a synonym dictionary per lang...
New
mudasobwa
After years of struggling I made StreamData dependency optional. Finitomata.ExUnit got testing primitives for Persistence. Full back co...
New
mudasobwa
MdexMultilineCells is an MDEx plugin enabling multi-line cells in Markdown tables with full inline/block Markdown rendering and automati...
New
sullyMusty
ActiveMemory 0.8.0 — in-memory tables that speak Ecto ActiveMemory 0.8.0 is now on Hex. ActiveMemory is an in-memory store built on ETS ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews