kip

kip

ex_cldr Core Team

Localize_ecto: locale-aware Postgres collation for Ecto queries

localize_ecto is the forgotten member of the elixir-localize family. When I announced Localize_mcp yesterday I had forgotten that I hadn’t actually published this either!

The most common database text sorting is usually byte or code-point order - Zebra before apple, öl after zebra - which isn’t a great experience for developers or users.

Postgres, however, ships the full set of ICU collations, and localize_ecto connects them to Ecto
with a collate/1,2 query macro that applies a COLLATE clause using the collation that best matches a Localize locale,

Sorting in ORDER BY

import Ecto.Query
import Localize.Ecto

# Sort by the current locale (Localize.get_locale/0)
from p in Product, order_by: collate(p.name)

# Or an explicit locale - runtime values pin as usual
from p in Product, order_by: collate(p.name, "sv")
# SELECT ... ORDER BY p0."name" COLLATE "sv-x-icu"`

The same data sorts differently per locale, as it should. German orders ä with a; Swedish puts it after z:

de: ["Apfel", "Äpfel", "öl", "Zebra"]
sv: ["Apfel", "Zebra", "Äpfel", "öl"]

Any valid locale finds its best available collation — "zh-TW" resolves to Postgres predefined zh-Hant-x-icu, "de-DE" to de-x-icu, "pt-BR" to pt-BR-x-icu.

Collating in SELECT and WHERE

collate/2 works anywhere a query expression is accepted, and it also collates comparisons like Postgres’s a < b COLLATE "de-x-icu" form:

# A collated expression in a select
from p in Product, select: collate(p.name, "de")

# A collated comparison in a select
from p in Product, select: collate(p.name < p.brand, "de")
# SELECT p0."name" < p0."brand" COLLATE "de-x-icu"

# And in a where clause`
from p in Product, where: collate(p.name < "münchen", "de"), select: p.name

German phonebook order, via a migration

Locales can carry a collation type in their -u-co- extension — German phonebook order treats ü as ue. PostgreSQL doesn’t predefine these, so localize_ecto provides reversible migration helpers:

defmodule MyApp.Repo.Migrations.AddPhonebookCollation do
  use Ecto.Migration
  import Localize.Ecto.Migration

  def change do
    create_collation("de-u-co-phonebk")
  end
end

After the migration, the locale just works in queries:

from p in Person, order_by: collate(p.surname, "de-u-co-phonebk")

If collated with the default collator would return ["Mahler", "Mueller", "Muller", "Müller"] but with the phonebook collator returns ["Mahler", "Mueller", "Müller", "Muller"].

More migration examples

# A custom name, used in queries with collate(p.name, collation: "german_phonebook")
create_collation("de-u-co-phonebk", name: "german_phonebook")`

# Natural sort: file1, file2, file10 — instead of file1, file10, file2`
create_collation("und-u-kn", name: "natural_sort")`

# Case-insensitive equality: 'HELLO' = 'hello' without citext or lower()`
create_collation("und-u-ks-level2", name: "case_insensitive", deterministic: false)`

# A collated index, so ORDER BY ... COLLATE is an index scan, not a sort`
create index("products", [collated(:name, "de")], name: :products_name_de)`

Notes

  • Postgres only.
  • The collations are deterministic (PostgreSQL’s default), so normalize your Unicode text (NFC) for expected equality semantics — the README has the details.
  • Two guides ship with the docs: Using Localize Ecto and Collations in PostgreSQL — the latter covers choosing a database default collation (short version: builtin C.UTF-8 on PostgreSQL 17+, C before that, and COLLATE in queries and indexes for linguistic behaviour).

Where Next?

Popular in Announcing Top

tmbb
PhoenixWS - Websockets over Phoenix Channels Source code on Github here: GitHub - tmbb/phoenix_ws: Websockets implemented over Phoenix Ch...
New
seancribbs
Today I released a new dialyzer Mix task as the dialyzex package! At the time we started writing this task, the existing dialyzer integra...
New
bradley
I’ve been working with Claude Code extensively and absolutely love it. However, I’ve come across the challenge of managing configuration ...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
Eiji
ExApi is a library that I’m developing now and hope release soon This library will allow to: list all apis list all api implementation...
New
aditya7iyengar
Rummage.Ecto and Rummage.Phoenix provide ways to perform Searching, Sorting and Pagination over Ecto queries and Phoenix collections. Fo...
New
treble37
Just looking for a little feedback on a tiny helper library I built - Sometimes I find the need to convert maps with atom keys to maps w...
New
michalmuskala
Another small library today. PersistentEts Hex: persistent_ets | Hex GitHub: GitHub - michalmuskala/persistent_ets · GitHub Ets table ...
New
archan937
It is a well-know topic within the Elixir community: “To mock or not to mock? :)” Every alchemist probably has his / her own opinion con...
New
handnot2
Samly can be used to enable SAML 2.0 Single Sign On in a Plug/Phoenix application. This library uses Erlang esaml to provide plug enabl...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31307 143
New

We're in Beta

About us Mission Statement