Internationalization via reified messages

Hi,

Somehow I didn’t find any PIP yet for Internationalization. I think SWI
prolog has a messaging infrastructure, which can also generated messages
in different languages. Probably inspired or inherited by Quintus Prolog.

I did once some research and somehow saw something similar in Quintus
Prolog. It also inspired Pillow, a web library, where output was seen as
producing a message. Recently it occured to me more and more that it

would be usefull to have multiple languages bundled into a single app.

Internationalization and Localization
The idea is often abbreviated to i18n (where 18 stands for the
number of letters between the first i and the last n in the word
internationalization, a usage coined at Digital Equipment
Corporation in the 1970s or 1980s.
https://en.wikipedia.org/wiki/Internationalization_and_localization

In some of my Prolog systems I started providing i18n databases via
a multifile strings/3 predicate, using some ISO local identifier logic to
access facts from the strings/3 predicate, in a manner that Java accesses

resource bundles. But to access Prolog error texts, I went a step further
and started dynamically matching template strings from strings/3 and
supply it to format/3. So layering the matter on format/3. But sometimes

it makes sense to fetch a i18n as an atom or string instead of sending it
to a stream. So I complemented the predicate put_message/[2,3] by a
predicate get_message/[2,3], the former tolerates missing template strings

the later indicates missing template strings by a failure. Here is a use
case of get_message/[2,3] not really using the failure and success status,
but rather showing the reification in the last argument, GNU time -f inspired:

time(Goal) :-
   get_message(time(fields), Format),
   time(Goal, Format).

strings('time.fields', '', '% %t, %p, %l').

SWI Prolog probably can do that as well, if it would redirect the output
stream to atom(A) or string(S). But get_message/[2,3] does reification without
redirection, so there are no multithreading reentrance issues or whatever.

Compared to SWI-Prolog put_message/[2,3] has also a smaller and more
focused scope than print_message/2, since it doesn’t have a formal “level”
parameter, not dealing with “error”, “warning”, etc.. messages.

Bye

BTW: Having all the i18n data as strings is sometimes more user friendly.

As a pragmatic developer, I would really appreciate tight integration with already existing gettext or catopen families of functions and tooling (for example poedit). I would like to see Prolog as a usual boring desktop programming language. But this will require standard FFI bindings to libc or complete re-implementation.

Can you tell us more about these libraries and their functionality.
My idea is basically that deep down it can be a hybrid of strings/3
FFI and the Prolog ISO knowledge bases, since strings/3 is multifile

but not necessarily dynamic. Namely the primary use is as a static
predicate, and for example use consult/1 or use_module/1 can even
load strings/3 definitions on demand. It basically utilizes the multifile

feature as defined in the ISO core standard. So it is a solution
that is portable, would work everywhere where you find a ISO
processor, and currently it requires also format/[2,3] and memory

streams to do the reification. There is a PIP now for format[2,3],
but I don’t see a PIP for memory streams yet:

Draft PIP-0110-format
https://discourse.prolog-lang.org/t/draft-pip-0110-format/170

Draft PIP-???-memory
https://discourse.prolog-lang.org/t/draft-pip-???-format/???

What is possible with the strings/3 approach to make a hybrid or
even drop strings/3 consulted from some Prolog texts altogether,
by tapping into some FFI. Namely you would to:

/**
 * strings(K, L, V):
 * The predicate succeeds in V with the value for the key K and the local L.
 */
% strings(+Atom, +Atom, -Atom)
:- multifile strings/3
strings(K, L, V) :-
   ffi_lookup_string(K, L, V).

You can mixin multiple resource bundle oracles via FFIs, since
the predicate is defined using the ISO core multifile directive.
The strings/3 predicate works with first argument indexing

in primitive Prolog systems already quite well, I use compressed
atomified key such as ‘time.fields’. It works a little better with
more advanced Prolog systems that have multi argument indexing,

since it would then also index the local. The challenge for a FFI is
to retain these Prolog traits, give ffi_lookup_string/3 a similar nice
performance profile, and extensibility as found via consult/1.

A more modern API is GNU gettext it is actually quite elaborate. Idea is that you as a programmer use plain english messages everywhere (but wrapped in a minimally obtrusive function call), and with the automagic power of the library they are translated into localized strings at runtime (it detects desired locale by checking LC_* or LANG environment variables). Also a big benefit is a tool called poedit – it can be used by non-programmers to write correct translations without knowing about how to code.

Libc catalogs is very simple (but very old) API that is bundled directly into libc itself – so it is available on all systems under the sun. Main function is catgets() which basically convertes two integers: catalog number and message number into localized string.

For prolog it can be something like:

% Outputs properly localized string (or EnglishText if not found) to user stream
% Sorry for operational description 
gettext(+EnglishText, ListOfTerms) :-
    getenv('LANG', UserLocale),
    find_string(default_catalogue, UserLocale, EnglishText, ListOfTerms).

Where EnglishText can contain formatting characters so underlying system can handle different word order and plura/singular differences.

Here is a less hastily written code sample:

%% gettext(+MsgId, -LocalizedText) is det.
%
% MsgId identifies the message to be translated, by convention it is an English version of the message.
% LocalizedText contains translation or MsgId if it was not found.
gettext(MsgId, LocalizedText) :-
    getenv_default('LANG', UserLocale, 'C'), % this can be cached somehow
    find_string(default_catalogue, UserLocale, MsgId, LocalizedText).

getenv_default(Var, Val, _) :- getenv(Var, Val), !.
getenv_default(_, Val, Val).

Usage example will be following:

?- gettext('~d files deleted.~n', L), format(L, [5]).
5 dosieroj forigitaj.
?-

What is the Prolog term that describes the content?
It violates the following Quintus Prolog idea:

The PIP here deals explicitly with this assumption borrowed from
Quintus Prolog, and only primarily asks for reification:

8.20.2 Implementation: Term-Based Messages
The message facility design is based on transforming message
terms to lists of format commands according to definite clause grammars.
https://quintus.sics.se/isl/quintus/pdf/quintus.pdf

I know I sound totally stubborn. But somehow I only want to clarify
the spec and the scope. Even if you have your gettext/2 somewhere,
you still have the problem to produce for example:

?- X is 1/0.
🚨 Fehler: Nulldivision.
    user auf 3

Since the ISO core standard throws error terms and not strings.
So while gettext/2 has some merrit for apps that use inline formatting,
print_message somehow demands to turn these inline formattings,

into Prolog terms that describe the formatting, and then from that
going back to formatting terms. Maybe this is a bad idea. I don’t know.
It doesn’t generate much overhead having Prolog terms that

describe formatting and to lookup or produce the formatting later,
instead of having a translation table between templates. But I
don’t want to advertize either, just clarify the spec and scope.

I get it, I haven’t even run this code, it is just to give a general feeling of how it can be used.

files_deleted(5) is indeed more idiomatic Prolog, my example is a one-to-one to GNU gettext interface. So if you would like to search for translations using that library as a string storage you have to use C strings at the FFI level anyway. But translation from term to atom is quite trivial.