Subject: Re: Re: internationalization support?

Krzysztof Królczyk <[email protected]> Thu, 15 Jan 2026 22:34:35 +0100
Newsgroups gmane.network.unison.devel
Message-ID <[email protected]>
This is a multi-part message in MIME format.
--===============8385169913547917416==
Content-Type: multipart/alternative;
 boundary="------------TAyFB6ta7dtPCsPaurlhoUAu"
Content-Language: en-US, de-DE, pl

This is a multi-part message in MIME format.
--------------TAyFB6ta7dtPCsPaurlhoUAu
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit

> My views:
>    - internationalization/translation would be great

Good to hear!

>    - I don't want to depend on non-Free tools including hosted translations.

both `ocaml-gettext` and its dependency camomile are LGPL2.1 so are 
compatible with GPL3 used by Unison (as per my knowledge, ianal ;))

>    - ocaml-gettext isn't already packaged in pkgsrc.  That doesn't mean I
>      think ocaml-gettext is not ok, but it does mostly mean that nothing
>      else in ocaml is that is packaged needs it.

I don't fully understand what do you mean - what exactly is `pkgsrc`?. 
"src" dir of Unison?
By packaging you mean, it's not vendored? Or that it hasn't been a 
dependency before?
(That is right, nothing depended on it)

>    - I would want to be able to build without ocaml-gettext.
>    - I'd be ok with checking for gettext and using it, more or less, on
>      systems where it works (subject to a build flag to say don't).  I'm
>      ok with a world where i18n works on some systems and not others, as
>      long as that's not structural so much as nobody has yet done the
>      per-OS work.

Right - while it is quite doable in makefile, to be honest i don't know 
how could one solve it on the OCaml side.
Either the strings would be duplicated (inside code and inside 
translation en.po file), or there could be a way to preprocess them back 
to the code.
What i mean is - if they are replaced inside the code with 
`(translate_function "message_id")` then code obviously wouldn't build 
without it.
One could do a replace, like: "for each `(translate_function "%s")` grep 
en.po, extract and replace in source.ml" however... it is:

a) quite ugly (messing with src where you might have already other 
changes, intended to be committed)
b) error prone, especially with multi-line strings
Duplication is also problematic for obvious sync reasons. Also, for the 
case someone wanted translations - it would need to replace the strings 
again...

Third approach - one could treat literal text as message id (so a key in 
a translation map/dictionary),
but this quite likely would bloat the translation files. I'm not quite 
sure if this would also work; it would still need to preprocess sources,
and convert `translate_function` calls to some `noop` function, which - 
when passed string argument would simply return it. Perhaps anyone has a 
better idea?
Fourth idea is even more funny. Every string is extracted to a separate 
module, and kept in a Map. Something more or less like:

```
|moduleAllStringsHolder =structletdummy x =x moduleStringMap 
=Map.Make(String)letmy_map =ref (StringMap.empty)letinitialize ()=my_map 
:=StringMap.empty|
|// here quite unpleasant initialization...|
|letget_entry key =StringMap.find_opt key !my_map letreplace_dummy f 
=dummy <-f end|
|```|
||
|If the internationalization module is build, it could replace dummy 
with it's `s_`. Maybe. If its not build, there is a lookup penalty...|


Last approach, it would need to be somewhat default.
Closely related task - *if this was made a hard dependency* - would be, 
how does one actually reliably extract strings from all 'ml' source files.
By hard dependency i mean, it would always depend on `gettext`, and a 
switch would just disable building languages other than default (EN).

So, yeah - maybe someone has a better idea.

> >/   - It likely would slightly complicate build process (currently uses />/OPAM gettext version downgraded to 0.4.2, because newer uses dune-site />/which wasn't cooperating) ///hmm, another dependency, but if unison uses it if it's built and not if
> it isn't probably is ok.

What do you mean? So gettext and gettext-camomile while two separate 
packages - are kind of single dependency to internationalization.
If you mean OPAM? Then it was already used by CI so i guess it is 
required. I'm not sure is it preferred to have a make target for deps
and simply run it for example from main Makefile like `opam_deps:\n\t 
opam install cairo2 lablgtk3 gettext.0.4.2 camomile.1.0.2` or - if issue 
above can be somehow resolved, then on demand.

> Feel totally free to open a draft PR if you have something that works,
> which will make it visible to all.  Others may wish to test and help.

Ok, PR #1170. Please let me know what you think, or in case of 
improvements, bugs ect.

--
Best regards,
Krzysztof
--------------TAyFB6ta7dtPCsPaurlhoUAu
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit

<!DOCTYPE html>
<html>
  <head>

    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <blockquote type="cite">
      <pre>My views:
  - internationalization/translation would be great</pre>
    </blockquote>
    <font face="monospace"><br>
      Good to hear!<br>
      <br>
    </font>
    <blockquote type="cite">
      <pre>  - I don't want to depend on non-Free tools including hosted translations.</pre>
    </blockquote>
    <br>
    <font face="monospace">both `ocaml-gettext` and its dependency
      camomile are LGPL2.1 so are compatible with GPL3 used by Unison
      (as per my knowledge, ianal ;))<br>
      <br>
    </font>
    <blockquote type="cite">
      <pre>  - ocaml-gettext isn't already packaged in pkgsrc.  That doesn't mean I
    think ocaml-gettext is not ok, but it does mostly mean that nothing
    else in ocaml is that is packaged needs it.</pre>
    </blockquote>
    <font face="monospace"><br>
      I don't fully understand what do you mean - what exactly is
      `pkgsrc`?. "src" dir of Unison?<br>
      By packaging you mean, it's not vendored? Or that it hasn't been a
      dependency before?<br>
      (That is right, nothing depended on it)<br>
      <br>
    </font>
    <blockquote type="cite">
      <pre>  - I would want to be able to build without ocaml-gettext.
  - I'd be ok with checking for gettext and using it, more or less, on
    systems where it works (subject to a build flag to say don't).  I'm
    ok with a world where i18n works on some systems and not others, as
    long as that's not structural so much as nobody has yet done the
    per-OS work.</pre>
    </blockquote>
    <font face="monospace"><br>
      Right - while it is quite doable in makefile, to be honest i don't
      know how could one solve it on the OCaml side.<br>
      Either the strings would be duplicated (inside code and inside
      translation en.po file), or there could be a way to preprocess
      them back to the code.<br>
      What i mean is - if they are replaced inside the code with
      `(translate_function "message_id")` then code obviously wouldn't
      build without it.<br>
      One could do a replace, like: "for each `(translate_function
      "%s")` grep en.po, extract and replace in source.ml" however... it
      is:<br>
      <br>
      a) quite ugly (messing with src where you might have already other
      changes, intended to be committed)<br>
      b) error prone, especially with multi-line strings<br>
      Duplication is also problematic for obvious sync reasons. Also,
      for the case someone wanted translations - it would need to
      replace the strings again...<br>
      <br>
      Third approach - one could treat literal text as message id (so a
      key in a translation map/dictionary),<br>
      but this quite likely would bloat the translation files. I'm not
      quite sure if this would also work; it would still need to
      preprocess sources,<br>
      and convert `translate_function` calls to some `noop` function,
      which - when passed string argument would simply return it.
      Perhaps anyone has a better idea?<br>
      Fourth idea is even more funny. Every string is extracted to a
      separate module, and kept in a Map. Something more or less like:<br>
      <br>
      ```</font><br>
    <font face="monospace"><code class="language-ocaml"
style="color: rgb(248, 248, 242); background: none; text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; tab-size: 4; hyphens: none;"><span
      class="token" style="color: rgb(139, 233, 253);">module</span><span> AllStringsHolder </span><span
      class="token" style="color: rgb(248, 248, 242);">=</span><span> </span><span
      class="token" style="color: rgb(139, 233, 253);">struct</span><span>
</span><span>  </span><span class="token"
      style="color: rgb(139, 233, 253);">let</span><span> dummy x </span><span
      class="token" style="color: rgb(248, 248, 242);">=</span><span> x
</span>
<span>  </span><span class="token" style="color: rgb(139, 233, 253);">module</span><span> StringMap </span><span
      class="token" style="color: rgb(248, 248, 242);">=</span><span> Map</span><span
      class="token" style="color: rgb(248, 248, 242);">.</span><span>Make</span><span
      class="token" style="color: rgb(248, 248, 242);">(</span><span>String</span><span
      class="token" style="color: rgb(248, 248, 242);">)</span><span>
</span><span>  </span><span class="token"
      style="color: rgb(139, 233, 253);">let</span><span> my_map </span><span
      class="token" style="color: rgb(248, 248, 242);">=</span><span> ref </span><span
      class="token" style="color: rgb(248, 248, 242);">(</span><span>StringMap</span><span
      class="token" style="color: rgb(248, 248, 242);">.</span><span>empty</span><span
      class="token" style="color: rgb(248, 248, 242);">)</span><span>
</span>
<span>  </span><span class="token" style="color: rgb(139, 233, 253);">let</span><span> initialize </span><span
      class="token" style="color: rgb(248, 248, 242);">(</span><span
      class="token" style="color: rgb(248, 248, 242);">)</span><span> </span><span
      class="token" style="color: rgb(248, 248, 242);">=</span><span>
</span><span>    my_map </span><span class="token"
      style="color: rgb(248, 248, 242);">:=</span><span> StringMap</span><span
      class="token" style="color: rgb(248, 248, 242);">.</span><span>empty</span></code></font><br>
    <font face="monospace"><code class="language-ocaml"
style="color: rgb(248, 248, 242); background: none; text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; tab-size: 4; hyphens: none;"><span>    // here quite unpleasant initialization...</span></code></font><br>
    <font face="monospace"><code class="language-ocaml"
style="color: rgb(248, 248, 242); background: none; text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; tab-size: 4; hyphens: none;"><span>
</span><span>  </span><span class="token"
      style="color: rgb(139, 233, 253);">let</span><span> get_entry key </span><span
      class="token" style="color: rgb(248, 248, 242);">=</span><span>
</span><span>    StringMap</span><span class="token"
      style="color: rgb(248, 248, 242);">.</span><span>find_opt key </span><span
      class="token" style="color: rgb(248, 248, 242);">!</span><span>my_map
</span>
<span>  </span><span class="token" style="color: rgb(139, 233, 253);">let</span><span> replace_dummy f </span><span
      class="token" style="color: rgb(248, 248, 242);">=</span><span> dummy </span><span
      class="token" style="color: rgb(248, 248, 242);">&lt;-</span><span> f
</span><span></span><span class="token"
      style="color: rgb(139, 233, 253);">end</span></code></font><br>
    <font face="monospace"><code class="language-ocaml"
style="color: rgb(248, 248, 242); background: none; text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; tab-size: 4; hyphens: none;"><span
      class="token" style="color: rgb(139, 233, 253);">```</span></code></font><br>
    <font face="monospace"><code class="language-ocaml"
style="color: rgb(248, 248, 242); background: none; text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; tab-size: 4; hyphens: none;"><span
      class="token" style="color: rgb(139, 233, 253);"></span></code></font><br>
    <font face="monospace"><code class="language-ocaml"
style="color: rgb(248, 248, 242); background: none; text-shadow: rgba(0, 0, 0, 0.3) 0px 1px; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; overflow-wrap: normal; line-height: 1.5; tab-size: 4; hyphens: none;"><span
      class="token" style="color: rgb(139, 233, 253);">If the internationalization module is build, it could replace dummy with it's `s_`. Maybe. If its not build, there is a lookup penalty...</span></code></font><br>
    <br>
    <br>
    Last approach, it would need to be somewhat default.<br>
    Closely related task - *if this was made a hard dependency* - would
    be, how does one actually reliably extract strings from all 'ml'
    source files.<br>
    By hard dependency i mean, it would always depend on `gettext`, and
    a switch would just disable building languages other than default
    (EN).<br>
    <br>
    So, yeah - maybe someone has a better idea.<br>
    <br>
    <blockquote type="cite">
      <pre>&gt;<i>    - It likely would slightly complicate build process (currently uses
</i>&gt;<i> OPAM gettext version downgraded to 0.4.2, because newer uses dune-site
</i>&gt;<i> which wasn't cooperating)
</i><i>
</i>hmm, another dependency, but if unison uses it if it's built and not if
it isn't probably is ok.</pre>
    </blockquote>
    <br>
    What do you mean? So gettext and gettext-camomile while two separate
    packages - are kind of single dependency to internationalization.<br>
    If you mean OPAM? Then it was already used by CI so i guess it is
    required. I'm not sure is it preferred to have a make target for
    deps <br>
    and simply run it for example from main Makefile like
    `opam_deps:\n\t opam install cairo2 lablgtk3 gettext.0.4.2
    camomile.1.0.2` or - if issue above can be somehow resolved, then on
    demand.<br>
    <br>
    <blockquote type="cite">
      <pre>Feel totally free to open a draft PR if you have something that works,
which will make it visible to all.  Others may wish to test and help.</pre>
    </blockquote>
    <br>
    Ok, PR #1170. Please let me know what you think, or in case of
    improvements, bugs ect.<br>
    <br>
    --<br>
    Best regards,<br>
    Krzysztof
  </body>
</html>

--------------TAyFB6ta7dtPCsPaurlhoUAu--

--===============8385169913547917416==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
Unison-hackers mailing list
[email protected]
https://LISTS.SEAS.UPENN.EDU/mailman/listinfo/unison-hackers

--===============8385169913547917416==--