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

sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New
bartblast
I’ll be using this thread to share Hologram patch release announcements. Minor releases will continue to get dedicated threads with blog ...
New
sorenone
This release unifies configuration for queues, repos, and services, swaps opaque timing integers for readable durations, and backports pe...
New
pcharbon
:heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::hea...
New
jvoegele
Bond brings Design by Contract to Elixir: preconditions, postconditions and invariants as executable specifications, checked at runtime a...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
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
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
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

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews