Stream: ideas

Topic: simple i18n


view this post on Zulip Luke Boswell (Nov 07 2024 at 00:48):

I was doing some research on i18n and discovered this rust crate, which got me thinking, how might this look in Roc.

I put this module together as a simple example, to see if people had any other ideas about how this might work?

https://gist.github.com/lukewilliamboswell/7cecc9cebf50436865752da96f4dada9

view this post on Zulip Luke Boswell (Nov 07 2024 at 00:59):

I could imagine in future this kind of module is code-generated from a list of text files that contain the translations required.

Using this in an app might look something like this...

main =

    locale = Locale.get!? {} |> Result.map I18n.fromLocaleStr

    Stdout.line (I18n.with Hello locale)

view this post on Zulip Agus Zubiaga (Nov 07 2024 at 01:41):

Unless you're translating the app yourself (which is unlikely), you probably want to pick a format that's supported by popular translation platforms.

view this post on Zulip Agus Zubiaga (Nov 07 2024 at 01:42):

I have experience using gettext and ICU, but Mozilla's Fluent looks pretty cool and modern too

view this post on Zulip Agus Zubiaga (Nov 07 2024 at 01:51):

Something I remember liking about gettext is that you can use the original language strings in the source code instead of having to define keys for everything

view this post on Zulip Agus Zubiaga (Nov 07 2024 at 01:52):

You just wrap translatable strings in a function, and then use xgettext to extract them from source and generate a template .pot file

view this post on Zulip Agus Zubiaga (Nov 07 2024 at 01:58):

At runtime, the strings are looked up in a binary .mo file that’s specific per locale. It’d probably be fine to load that entirely into memory so that string lookups don’t have to be effectful.

view this post on Zulip Luke Boswell (Nov 07 2024 at 01:59):

Ive thought about how we might be able to use compile time evaluation here, but haven't had any significant brainwaves -- getting the locale is a runtime thing

view this post on Zulip Agus Zubiaga (Nov 07 2024 at 02:00):

Yeah, I don’t think you want a binary per locale

view this post on Zulip Luke Boswell (Nov 07 2024 at 02:00):

I imagine the application has the translations in the (or one of the) binary encoded file(s) and then loads and decodes that and passes it in as a module param or something

view this post on Zulip Agus Zubiaga (Nov 07 2024 at 02:01):

and you probably don’t want to include all locales in the binary either, but that depends on the app

view this post on Zulip Agus Zubiaga (Nov 07 2024 at 02:04):

IIRC Linux (executable) binaries include the original language strings only, which are then used to look up the locale-specific string in the .mo file if available

view this post on Zulip Agus Zubiaga (Nov 07 2024 at 02:04):

I might be wrong though, it’s been a while since

view this post on Zulip Jasper Woudenberg (Nov 07 2024 at 07:40):

For the 'location-string-as-a-function' approach Agus mentioned, an API like this might be nice to use:

# Translations.roc
module { locale } -> [hello, bye]

hello : { name : Str } -> Str
hello = \{ name } ->
    when locale is
        En -> "Hello, $(name)"
        Fr -> "Bonjour, $(name)"

bye : Str
bye =
    when locale is
        En -> "Bye!"
        Fr -> "Au revoir!"

view this post on Zulip Jasper Woudenberg (Nov 07 2024 at 07:49):

Or maybe a record would be easier to dynamically load files

Translations : {
    hello: { name : Str } -> Str,
    bye: Str,
}

english = {
    hello: \{ name } -> "Hello, $(name)",
    bye: "Bye!",
}

french = {
    hello: \{ name } -> "Bonjour, $(name)",
    bye: "Au revoir!",
}

view this post on Zulip Jasper Woudenberg (Nov 07 2024 at 07:52):

A combined approach:

# Translations.roc
module { locale } -> [hello, bye]

hello : { name : Str } -> Str
hello = locale.hello

bye : Str
bye = locale.bye

Here the Locale type would (secretly) contain the translations for that locale, and the Translations.roc module would just be a wrapper for nice access to translations.

view this post on Zulip Hannes (Nov 08 2024 at 12:12):

A while ago I started work on some i18n in Roc using the CLDR data, it turns out there's a lot of locales and the Roc compiler at the time didn't like compiling a 4MB file :sweat_smile:

https://github.com/Hasnep/roc-cldr/blob/main/src/Date.roc


Last updated: Jun 16 2026 at 16:19 UTC