Around The World

Martin Kompf

Die »Locale« Funktionen dienen zur Anpassung der Standard C Library an länderspezifische Gegebenheiten.

Soll ein Softwareprogramm in andere Länder exportiert werden, so muss der Entwickler eine Reihe von Besonderheiten berücksichtigen, die aufgrund der andersartigen Sprache und Kultur auftreten. Insbesondere betrifft dies das Datumsformat, die Formate von Zahlen und Währungen, die Sortierreihenfolge, den Zeichensatz und natürlich die Übersetzung von Benutzermeldungen. Zur Unterstützung dieser Funktionen gibt es in der Standard C Library den Begriff der »Locale«, im deutschen Sprachgebrauch spricht man in diesem Zusammenhang auch von der »Lokalisierung« einer Software.

Leider ist die Art und Weise der Locale-Unterstützung stark vom Betriebssystem abhängig, die nachfolgenden Ausführungen beziehen sich daher hauptsächlich auf Linux mit der glibc 2. An einigen Stellen wird auf die Unterschiede zu Windows hingewiesen.

Immer dabei: Die C Locale

Standardmäßig verwendet ein Programm die sogenannte »C« Locale. Diese ist die einzige auf allen Betriebssystemen mit Sicherheit vorhande Locale. Sie bietet allerdings nur Unterstützung für den 7 bit Zeichensatz, Datumsausgaben erfolgen gemäß den amerikanischen Konventionen in der Form Tuesday 08/14/01 16:01:22. Arbeitet ein Programm mit deutschen Umlauten oder soll die Datumsangabe in der im Inland gebräuchlichen Form erfolgen, so ist die C Locale ungeeignet.

Setzen einer Locale

Eine Lokalisierung des Programmes erfordert zunächst das Setzen der richtigen Locale sowie die Verwendung ganz bestimmter Funktionen, zum Beispiel zur Datumsformatierung oder zum Vergleich von Zeichenketten:

#include <locale.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
 
int main()
{
    time_t t;
    char buffer[256];
    double val = 1234567.89;
    
    /* Setze die Locale */
    char *locale;
    locale = setlocale( LC_ALL, "");
    if (NULL == locale) {
        printf( "unable to set locale\n");
    }
    else {
        printf( "current locale is %s\n", locale);
    }
 
    /* Datumsformat (strftime) */
    time(&t);
    strftime( buffer, sizeof(buffer), "%A %x %X", localtime(&t));
    printf( "%s\n", buffer);
 
    /* Numerisches Ausgabeformat (printf) */
    printf( "%12.2f\n", val);
 
    /* Währungsformat (strfmon) */
    strfmon( buffer, sizeof(buffer), "%20.2i", val);
    printf( "%s\n", buffer);
}

Das Setzen der Locale erfolgt mit der Funktion setlocale. Der erste Parameter ist die Kategorie, für die die Lokalisierung angewendet werden soll. Das im Beispiel verwendete LC_ALL bedeutet »alle Kategorien«, alternativ dazu könnte man beispielsweise mittels LC_TIME die Anwendung der Lokalisierung nur auf Datums- und Zeitfunktionen einschalten. Eine Liste der möglichen Parameter findet sich in der man-Page von setlocale.

Der zweite Parameter ist der Name der zu verwendenden Locale. Wird hier eine leere Zeichenkette angegeben, so verwendet setlocale den Wert der entsprechenden Umgebungsvariablen, im Beispiel LC_ALL. Dieser Wert ist eine Zeichenkette, die aus dem Sprachcode nach ISO 639, dem Unterstrich _ und dem ISO 3166 Ländercode besteht. Zusätzlich kann man noch einen Punkt . und den Namen des Zeichensatzes anhängen.

Beispielwerte für LC_ALL

de_DE Deutsch
de_DE.UTF-8 Deutsch mit UTF-8 Zeichensatz kodiert
en_US amerikanisches Englisch
en_EN britisches Englisch
de_CH schweizer Deutsch
it_CH schweizer Italienisch
es_ES Spanisch
ca_ES Katalan
C     Standard C Locale

Mehrsprachig

Probieren wir doch einmal unser Testprogramm mit verschiedenen Werten für LC_ALL aus:

$ export LC_ALL
$ LC_ALL=de_DE
$ loctest
current locale is de_DE
Dienstag 14.08.2001 16:43:48
  1234567,89
    DEM 1.234.567,89
$ LC_ALL=de_CH
$ loctest
current locale is de_CH
Dienstag 2001-08-14 16:47:58
  1234567,89
   CHF  1.234.567,89
$ LC_ALL=it_CH
$ loctest
current locale is it_CH
martedì 14. 08. 01 16:50:28
  1234567,89
   CHF  1.234.567,89
$ LC_ALL=en_US
$ loctest
current locale is en_US
Tuesday 08/14/01 04:48:48 PM
  1234567.89
    USD 1,234,567.89

Es ist also möglich, das Programm nur durch Setzen einer Umgebungsvariablen an die verschiedensten Sprachen und Länder anzupassen! Und das alles ohne eine einzige Zeile Code zu verändern!

Das beschriebene Vorgehen funktioniert prinzipiell auch unter Windows. Jedoch geht hier das Setzen der Locale über eine Umgebungsvariable nicht. setlocale(LC_ALL, "") verwendet stattdessen immer die Standardspracheinstellungen des Betriebssystems. Siehe dazu Locale Names, Languages and Country/Region Strings in der MSDN Library.

Sortierungen

Besonders wichtig ist das korrekte Setzen der Locale auch in Programmen, die bei der Sortierung von Wörtern auf nationale Besonderheiten, wie Umlaute, Rücksicht nehmen sollen. Das folgende Programmfragment sortiert eine deutsche Wortliste mittels des Standard C Library Algorithmus qsort:

/* Hilfsfunktion für qsort - vergleicht zwei Strings mittels strcoll */
int comp( const void *p1, const void *p2)
{
    int res;
    res =  strcoll( *(char **)p1, *(char **)p2);
    return res;
}
 
/* ... */
 
    int i;
    char *words[] = {"Abfahrt", "Abfuhr", "Abführmittel", "Abfälle", "Abfall"};
 
    /* Sortierung (qsort) */
    qsort( words, 5, sizeof(char *), comp);
    for (i = 0; i < 5; ++i) {
        printf( "%s\n", words[i]);
    }

Ohne weitere Maßnahmen werden die Wörter in der Reihenfolge

Abfahrt
Abfall
Abfuhr
Abfälle
Abführmittel

sortiert. Das ist nicht korrekt, da der Buchstabe ä im deutschen vor u einsortiert werden muss. Erst durch Einbau der setlocale Funktion in das Programm und setzen von LC_ALL oder LC_COLLATE auf de_DE wird die in Deutschland übliche Sortierreihenfolge verwendet:

Abfahrt
Abfall
Abfälle
Abfuhr
Abführmittel

Man beachte, dass wie im Beispiel gezeigt, die Funktion strcoll zum Vergleich zweier Zeichenketten verwendet werden muss (und nicht etwa strcmp)!

Damit sind aber noch nicht alle Aspekte der Lokalisierung abgehandelt. So könnte es möglich sein, dass bestimmte Locales, wie zum Beispiel Japanisch (ja_JA), mehr als 256 verschiedene Schriftzeichen verwenden. Ein einzelnes Zeichen lässt sich dann nicht mehr als Datentyp char abspeichern. Man kann dies durch Abfrage der Variablen MB_CUR_MAX zur Laufzeit testen. Wird diese größer als eins, dann muss das Programm Zeichenketten unter Verwendung des Datentyps wchar_t behandeln. Dies wird das Thema eines weiteren Artikels an dieser Stelle sein.

Ein anderer wichtiger Punkt ist die Übersetzung von Benutzerausgaben in alle möglichen Sprachen. Hier können die Locale Funktionen aus der Standard C Library natürlich nicht mehr weiterhelfen, wahrscheinlich muss zunächst ein Übersetzer hinzugezogen werden. Es gibt allerdings eine Reihe von Utilities, die bei der programmtechnischen Verwaltung multilingualer Textausgaben helfen, wie zum Beispiel GNU gettext. Die hierzu vorhandene Dokumentation gibt auch eine sehr gute Einführung in die Thematik der Internationalisierung und Lokaliserung von Software.