geo2.pl

I was using geo2.pl GPS/GPRS location tracking device for some time. Wasn’t best experience – frequent system failures (on device and on geo2 server side), lack of competent support, lack of information to users (once it died for a month without ANY information from geo2 company). So if you considered buying geo2 product – forget about it. With such quality it’s not worth your money. Especially that they rose monthly price over 300%. But if you have hardware already…

Hardware:

There are two serial ports available on this unit. One provides GPS coordinates in NMEA format and is available through USB connector outside the device. The other serial port is available internally at JP1 connector (see picture). Any USB-serial adapter will do (I used Profilic PL2303 based one in form of old Nokia CA42 cable).

Use internal JP1 serial port to access Telit GE683-GPS AT command interface. By default units starts internal geo2 software (written in python).

AT#LSCRIPT

#LSCRIPT: “config”,53
#LSCRIPT: “updater.log”,247
#LSCRIPT: “gsm.pyo”,5085
#LSCRIPT: “main.pyo”,10393
#LSCRIPT: “config.pyo”,1602
#LSCRIPT: “protocol.pyo”,11566
#LSCRIPT: “acc.pyo”,1565
#LSCRIPT: “kernel.pyo”,8684
#LSCRIPT: “device.pyo”,2207
#LSCRIPT: “gps.pyo”,4347
#LSCRIPT: “io.pyo”,1197
#LSCRIPT: “List.pyo”,6015
#LSCRIPT: “debug.pyo”,2394
#LSCRIPT: “boot.py”,27
#LSCRIPT: free bytes: 1944638

OK

“boot.py” is started after powering on geo2 device. You can change boot mode to “start after 10 seconds” and also change boot file to not existing file to prevent it from running any script. Finally reboot device. You have few seconds after powering on geo2 device to do that – otherwise boot.py will start.

AT#STARTMODESCR=1,10
AT#ESCRIPT=”blah.py”
AT#REBOOT

Now see what geo2 software prints for us. We will start “boot.py” without a reboot:

AT#ESCRIPT=”boot.py”
OK
AT#EXECSCR
OK
kernel start
import main # precompiled from main.pyo
import protocol # precompiled from protocol.pyo
import marshal # builtin
import gps # precompiled from gps.pyo
import gsm # precompiled from gsm.pyo
import config # precompiled from config.pyo
import device # precompiled from device.pyo
import List # precompiled from List.pyo
import acc # precompiled from acc.pyo
import IIC # builtin
import io # precompiled from io.pyo
main
main

(now it will periodically print “main” until some bad thing happens).

Can we see what geo2 software does? Yes, we can. There is “AT#RSCRIPT# command that prints content of the file. Unfortunately geo2 files were uploaded with a option disabling RSCRIPT for most of uploaded files.

Fortunately we can read the files from own python script. Someone wrote scripts for this already. telit-pyo.py script from telit-862-python-tools github repository when run on Telit device will print content of all “*.py” scripts to serial console output (in hex, with additional headers). Then decode-telit-pyo.py script will produce raw files from serial console log.

We need small modifications for our purposes – we have to patch telit-pyo.py script to also print content of “config” and “*.pyc” files (and also skip printing itself).

With this we end up having all files from geo2 device. There is another problem – most of these is “pyc” which is not raw source code. It’s python byte code, not really human readable.

decompyle – Python Decompiler comes to help. It’s not developed anymore (there is paid commercial service based on it though) and hard to compile. I’ve built it with python 1.6 and 2.7 after some patching.

With decompyle we can get more readable form of byte code files.

First look and – quality of geo2 code seems to be quite low.

GPRS part registers with “apn.o2.pl” APN (previously geo2 company was owned by o2.pl and, no, it wasn’t much better experience then) to a PlusGSM network.

Device talks with IP 193.17.41.249 on port 6288 (TCP connection). There seems to be no real authorization (yikes!) when talking to the server. Pseudo authorization is based on device IMEI and value stored in config file (as “CODE”).

GPS when no fix is found is… restarted after some time (300s), then restarted again, and again, and again (with longer periods each time).

Accelerometer is accessed over I2C and used to detect if device is moving (and then transmit new coordinates). It is initialized with such values:

_ADDRESS = 28
_INIT = ‘g’
_FILTER = ‘\x07’
_SENSITIVITY = ‘\x04’

# i2c device at _ADDRESS
_i2c.readwrite((‘ %s’ % (_INIT)), 0)
_i2c.readwrite((‘!%s’ % (_FILTER)), 0)
_i2c.readwrite((‘2%s’ % (_SENSITIVITY)), 0)

Configuration is stored in “config” file. This file can be read directly with “AT#RSCRIPT”.

[
3, # CONF_VER
‘8\xff)’, # CODE (used in authorization header)
”, # PIN
1, # ROAM_OFF
0, # ROAM_ZONE_ID
[], # ROAM_ZONE
0, # ROAM_STATE
-1, # JAMMER_DATE
0 # DELAY
]

The device has a code that allows to run (via eval()) ANY code that’s sent from remote geo2 server (idea was to allow remote debugging I think). Good that there is no microphone available on the geo2 device since Telit chip is capable of establishing audio channel and thus allowing remote audio spying.

It is also possible to upload few version of software files remotely. “updater.log” stores some information about the process:

starting update
version /fizyka/thingy/trunk/geo2b105:1091
L5;L8;L10 gsm.pyo;L14 main.pyon;L10 config.pyo;L14 protocol.pyon;L10 acc.pyo;L14 kernel.pyon;L14 device.pyon;L14 gps.pyon;L10 io.pyo;L10 List.pyo;L10 debug.pyo;L17;L21;L25;L27;L29;L30;

All that information and great Telit documentation will allow you to write own software. You could also change geo2 IP to your own and simulate geo2 server software on own machine.

Mounting DOS HDD image

Mounting dos hdd image, first partition:

mount -o loop,offset=32256 dos-hdd.img dir

I’m using such image to update BIOSes in machines where bios doesn’t fit into floppy image. Such hdd image can be places inside iso image to be booted at virtual or real cdrom.

mkisofs -v -r -T -J –hard-disk-boot -b dos-hdd.img -o ../test.iso .

Tabs, vim and 256 colors – tips

Having tabs and spaces visible as separate entities in vim is very neat feature especially if you use colors to differentiate.

Switch TERM to konsole-256color in kde konsole terminal emulator (or whatever emulator you use – rpm -ql ncurses terminfo | grep 256 for more) and put into .vimrc:


colorscheme darkblue
highlight TabGroup ctermbg=233 guibg=233
match TabGroup /\t/

Now tab characters are a bit brighter than spaces.

lftp and editing remote files

lftp is a very nice piece of software (for people who like text console).

How to edit remote files? Take a look!


[arekm@tarm ~]$ cat .lftp/rc
alias edit source -e ~/.lftp/edit.sh
[arekm@tarm ~]$ cat .lftp/edit.sh
#!/bin/sh
tempid=$$
echo get $1 -o /tmp/$tempid$1
echo shell vim /tmp/$tempid$1
echo put -E /tmp/$tempid$1 -o $1
[arekm@tarm ~]$ lftp -u arm ftp.somewhere.pl
Password:
lftp arm@ftp.somewhere.pl:~> ls
drwxr-xr-x 2 0 0 4096 Jan 29 20:35 .
drwxr-xr-x 2 0 0 4096 Jan 29 20:35 ..
lftp arm@ftp.somewhere.pl:~> edit test.txt
get: Access failed: 550 Can't open test.txt: No such file or directory

[HERE vim is opened; after saving]

9 bytes transferred
lftp arm@ftp.somewhere.pl:~> rels
drwx---r-x 3 10089 999 50 Jun 15 18:35 .
drwxr-xr-x 2 0 0 4096 Jan 29 20:35 ..
-rw-r--r-- 1 10089 nogroup 9 Jun 15 18:35 test.txt
lftp arm@ftp.somewhere.pl:~> cat test.txt
El test.
10 bytes transferred
lftp arm@ftp.somewhere.pl:~> edit test.txt
9 bytes transferred

[HERE again vim is opened; after saving]

23 bytes transferred
lftp arm@ftp.somewhere.pl:~> cat test.txt
El test.
Small change.
25 bytes transferred
lftp arm@ftp.somewhere.pl:~>

Don’t we all love lftp?

201504 EDIT: lftp 4.6.1 will have edit command built in!

CVS – System Kontroli Wersji

CVS – System Kontroli Wersji

Arkadiusz Miśkiewicz
$Id: cvs-art.txt,v 1.7 2000/05/07 17:39:00 misiek Exp $Większość dostępnych programów oferowanych użytkownikom Linuksa jest
dziś tworzonych przez duże grupy programistów. Zarządzanie takimi projektami
jak jądro Linuksa, glibc czy XFree86 było by bardzo trudne i uciążliwe gdyby
nie pomysłowi programiści, którzy stworzyli narzędzie CVS.

CVS jest efektywnym systemem kontroli wersji. System zapamiętuje
wszystkie wersje wszystkich plików, które umieścisz w CVSie.

(more…)

GNU gettext z punktu widzenia programisty oraz tłumacza

GNU gettext dla programisty i tłumacza

Arkadiusz Miśkiewicz

$Id: gettext-art.lyx,v 1.1 2000/03/12 21:12:04 misiek Exp $

1 Wprowadzenie

Systemy UNIX, a w szczególności Linux zdobywają coraz większą popularność
na świecie. Linux używany jest zarówno przez wyszkolonych administratorów
jak i początkujących. Kilku programistów wychodząc na przeciw początkującym
użytkownikom nie znającym języka angielskiego stworzyło pakiet GNU
gettext. GNU gettext jest zbiorem aplikacji oraz bibliotek przeznaczonych
do tworzenia programów oraz skryptów potrafiących komunikować się
z użytkownikiem w dowolnym (obsługiwanym) języku.

2 Jak to działa ?

GNU gettext działa na zasadzie modułowej. Każde nowe tłumaczenie programu
jest osobnym plikiem tzw. plikiem MO (jeden plik dla każdego języka).
W większości dystrybucji Linuxa tego typu pliki można znaleźć w katalogach
/usr/share/locale/*/LC_MESSAGES/. Załóżmy, że jakiś program wyświetla
tekst “Hello world!”. Gdy ów program będzie wykorzystywał gettext
zostaną wykonane następujące operacje:

1. sprawdź aktualne zmienne locale (opisane w locale(7)). Najczęściej
używa się zmiennych LC_ALL oraz LANG.

2. sprawdź czy istnieje odpowiedni plik MO dla języka określonego w
zmiennych locale.

3. jeśli istnieje – wczytaj i używaj tłumaczeń; jeśli nie istnieje –
użyj domyślnego języka (w naszym przykładzie jest to język angielski).

Zaletą takiego rozwiązania jest to, że na jednym systemie może pracować
kilkunastu użytkowników używających różnych języków ! Ponadto dodanie
nowego tłumaczenia to tylko dodanie jednego niewielkiego pliku. Jeśli
czytelnik zaglądał to któregoś z plików MO to zapewne wie iż jest
to plik binarny. Pliki binarne MO generowane są z plików tekstowych
PO (zazwyczaj dostępnych wraz ze źródłami programu) przez niewielki
program ,,msgfmt” należący do pakietu gettext.

3 Chcę zostać tłumaczem.

Pliki tekstowe PO są to pliki zawierające wszystkie komunikaty w języku
domyślnym oraz (po przetłumaczeniu) komunikaty w języku docelowym
(np. polskim). Fragment typowego pliku PO przed tłumaczeniem wygląda
mniej więcej tak (w nawiasach klamrowych moje komentarze, których
nie powinno być w oryginalnym pliku PO):

#: src/man-config.c:233
{ nazwa pliku i numer lini, w której występuje podany tekst }
#, c-format
{ dodatkowe informacje o tekście }
msgid “Reading config file %s\n”
{ sam tekst w języku domyślnym }
msgstr “”
{ miejsce na tekst w języku docelowym }

Praca tłumacza sprowadza się do przetłumaczenia tekstu znajdującego
się po polu msgid na język docelowy. Uwaga: nie wolno zmieniać tekstu
znajdującego się po polu msgid ! Powyższy fragment po przetłumaczeniu
może wyglądać tak:

#: src/man-config.c:233
#, c-format
msgid “Reading config file %s\n”
msgstr “Odczytuję plik konfiguracyjny %s\n”

Powyżej, w komentarzu do ,,c-format” napisałem o ,,dodatkowych informacjach
o tekście”. W miejscu tym może się pojawić kilka słów kluczowych:

* fuzzy. Generowane przez program msgmerge (o którym później). Oznacza
to iż prawdopodobnie tłumaczenie nie jest prawidłowe. Po skontrolowaniu
poprawności tłumaczenia usuwamy słowo fuzzy z pliku PO.

* c-format, no-c-format. Dodawane automatycznie (nie powinny być dodawane
czy modyfikowane przez użytkownika). Odpowiednio oznaczają, że tekst
zawiera znaki formatujące z języka C, oraz że ich nie zawiera. Słowo
c-format jest jednocześnie sygnałem dla msgfmt by zwrócił większą
uwagę na poprawność tłumaczenia tegoż tekstu podczas generowania
plików MO.

Przed zabraniem się do tłumaczenia pliku PO pochodzącego z jakiegoś
programu należy zaktualizować plik PO. W typowym programie wykorzystującym
autoconf’a wygląda to mniej więcej tak: ./configure && make -C po
update-po. Oczywiście można wygenerować nowy plik PO (bez żadnych
tłumaczeń) wykonując ./configure && make -C po Nazwa_Programu.pot.
Powstanie plik *.pot – zwykły plik PO nie zawierający żadnych tłumaczeń
(POT == PO Template). Pozostaje jedynie zabranie się za tłumaczenie
pliku. Przyjęto zasadę by w tłumaczeniach używać form bezosobowych.

Poprawność syntaksy przetłumaczonego pliku PO można sprawdzić przy
użyciu msgfmt: msgfmt -v plik.po -o /dev/null. Program poinformuje
o wszelkich nieprawidłowościach. Rola tłumacza właściwie się tu kończy.
Przetłumaczony plik należy przesłać do odpowiedniej grupy tłumaczy
GNU (np. polska grupa tłumaczy GNU jest dostępna pod adresem: pl@li.org)
lub autora programu.

4 Piszę program i chcę dodać obsługę wielu języków.

Poniższy opis dotyczy nie tylko autorów nowych programów ale także
wszystkich osób, które chcą dodać obsługę języków do już istniejącego
oprogramowania. Na początek parę słów o programach oferowanych w pakiecie
GNU gettext:

– gettextize; wykonanie tego skryptu w katalogu, w którym znajduje
się nasz program spowoduje stworzenie struktury katalogów i skopiowanie
standardowych plików używanych przez gettext podczas kompilacji itp.
Ponadto skopiowane zostaną źródła biblioteki libintl na wypadek gdyby
systemowa biblioteka nie obsługiwała funkcji gettext() i jej towarzyszących.

* msgcmp; porównuje dwa pliki PO, żeby sprawdzić czy zawierają te same
zbiory łańcuchów msgid.

* msgcomm; wyszukuje podobne zbiory łańcuchów w plikach PO

* msgfmt; generuje binarne pliki MO z plików tekstowych PO

* msghack; do automatycznej zmiany plików PO np. zamiany miejscami
zbiorów msgid z msgfmt.

* msgmerge; łączy dwa pliki PO, tak by wynikowy plik zawierał maksimum
tłumaczeń z podanych dwóch plików

* msgunfmt; funkcja odwrotna do msgfmt

* xgettext; generuje plik PO z tekstami do przetłumaczenia wynajdując
je w plikach źródłowych programu (*.c itp).

Część z powyższych programów nie wchodzi w skład standardowej dystrybucji
GNU gettext i jest dostępna w formie patchy (pełny pakiet m.in. w
PLD GNU/Linuxie).

Programiście pakiet gettext oferuje kilka funkcji:

char *textdomain (const char *domain_name);

– funkcja ustala tzw. TEXTDOMAIN. Tłumaczenia będą pobierane z pliku
TEXTDOMAIN.mo z katalogu zależnego od ustawień systemowych (typowo
jest to /usr/share/locale/).

char *bindtextdomain (const char *domain_name, const char *dir_name);

– funkcja ,,przydziela” danej domenie (domain_name) katalog, w którym
będzie poszukiwany plik TEXTDOMAIN.mo (na wypadek gdyby występowało
kilka plików *.mo o tej samej nazwie).

char *gettext (const char *msgid);

– funkcja, której przekazujemy string do tłumaczenia.

char *dgettext (const char *domain_name, const char *msgid);
char *dcgettext (const char *domain_name, const char *msgid, int category);

– funkcje będące połączeniem funkcji gettext() z bindtextdomain();.

Poza powyższymi funkcjami wykorzystuje się jeszcze jedną zawartą w
bibliotece glibc – setlocale(3). Funkcja ta służy do ustawienia aktualnie
używanego locale. Prototyp tej funkcji to:

char *setlocale(int category, const char *locale);

gdzie category to nazwa kategorii dla jakiej ustawiamy locale (typowo
jest to LC_ALL lub LC_MESSAGES) natomiast locale może być zarówno
pustym stringiem “” (wtedy locale zostanie ustawione zgodnie z wartościami
zmiennych powłoki odpowiedzialnych za locale tj. LC_ALL, LC_MESSAGES
itd) lub stringiem oznaczającym język dla jakiego ustawiamy locale
(np. “da_DK”). Ponieważ w większości wypadków program powinien wykorzystywać
zmienne powłoki dlatego będziemy używali pustego stringu “” jako locale
w funkcji setlocale(3).

Dla ułatwienia sobie pracy zamiast funkcji gettext będziemy używali
makra _(). Ponadto zdefiniujemy puste makro pełniące funkcję informatora
dla programu xgettext (o czym później).

#define _(String) gettext(String)
#define N_(String) (String)

Dodajmy więc obsługę gettextu do przykładowego programu:

#include
int main()
{
printf(“Hello world!\n”);
exit(0);
}

Po pierwsze musimy włączyć dodatkowe pliki nagłówkowe: “libintl.h”
(zawiera funkcje typowe dla gettextu) oraz “locale.h” (prototyp funkcji
setlocale(3)). Następnie dodajemy ustawienie locale, przydzielenie
TEXTDOMAIN, a we wszystkie teksty, które mają być tłumaczone przekazujemy
do funkcji gettext() (w naszym wypadku za pośrednictwem makra _()).
W efekcie plik będzie wyglądał następująco:

#include
#include #include

#define _(String) gettext(String)

int main()
{
setlocale(LC_ALL, “”);
bindtextdomain(“example”, “/usr/share/locale”);
textdomain(“example”);
printf(_(“Hello world!\n”));
exit(0);
}

Uważny czytelnik zapewne zauważył, że puste makro N_() nigdzie nie
zostało użyte – więc po co ono jest potrzebne. Otóż program xgettext
(o którym pisałem wcześniej) generuje plik PO wyszukując w plikach
*.c teksty przekazywane do funkcji gettext(). W naszym programie przykładowym
xgettext znalazł by tekst “Hello world!\n”. Niestety w pewnych okolicznościach
nie można bezpośrednio używać funkcji gettext() np. mamy:

char *tablica[] = { “dog\n”, “cat\n”, “Poland\n” };
[…]
int main()
{
int i;
[…]
for (i = 0; i < 3; i++) printf(tablica[i]); } Użycie funkcji gettext() w definicji "tablica" jest oczywistym błędem programisty. Wyjściem z tej sytuacji jest właśnie makro N_(). Powyższy program w wersji z obsługą gettextu: #define _(String) gettext(String) #define N_(String) (String) char *tablica[] = { N_("dog\n"), N_("cat\n"), N_("Poland\n") }; [...] int main() { int i; [...] /* tutaj powinny znaleźć się funckcje setlocale(), textdomain() itd */ for (i = 0; i < 3; i++) printf(_(tablica[i])); } xgettext dzięki N_() będzie teraz ,,wiedział'', które teksty są przeznaczone do tłumaczenia, a sama funkcja gettext() zajmie się tłumaczeniem. No dobrze. Mamy już pliki źródłowe zawierające odpowiednie wywołania funkcji gettext(). Jak teraz wygenerować plik PO ? Właśnie w tym momencie użyjemy programu xgettext: $ xgettext -d example -o example.po -s plik1.c plik2.c plik3.c W wyniku działania programu xgettext (na pliku z pierwszym przykładem - "Hello world" otrzymamy plik PO o następującej zawartości): # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Free Software Foundation, Inc. # FIRST AUTHOR , YEAR.
#
#, fuzzy
msgid “”
msgstr “”
“Project-Id-Version: PACKAGE VERSION\n”
“POT-Creation-Date: 2000-03-10 22:28+0100\n”
“PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n”
“Last-Translator: FULL NAME \n”
“Language-Team: LANGUAGE \n”
“MIME-Version: 1.0\n”
“Content-Type: text/plain; charset=CHARSET\n”
“Content-Transfer-Encoding: ENCODING\n”

#: przyklad1.c:17
msgid “Hello world!\n”
msgstr “”

Po przetłumaczeniu pliku PO generujemy plik MO:

$ msgfmt example.po -o example.mo

i kopiujemy go do katalogu /usr/share/locale/JĘZYK/LC_MESSAGES/ (gdzie
JĘZYK to kilkuliterowa nazwa oznaczająca określony język. Dla języka
polskiego będzie to “pl”).

Pozostaje już tylko podziwianie efektów naszej pracy:

$ LANG=pl ./przyklad1
Witaj świecie!
$ LANG=C ./przyklad1
Hello world!

(“C” – oznacza domyślny język ustawiony w bibliotece systemowej; typowo
jest to język angielski).

Programiści stosujący pakiety automake oraz autoconf w swych programach
mogą używać paru ułatwień związanych z gettextem. Po pierwsze należy
uruchomić program gettextize (o którym już pisałem):

~/moj_program/$ gettextize -c -f

Do configure.in dodajemy:

ALL_LINGUAS=”pl” – lista języków dla których już posiadamy tłumaczenie
(oddzielone spacją np. “pl fr de”)

AM_GNU_GETTEXT – makro robiące całą czarną robotę i przegenerowujemy
skrypt configure (wywołując program: autoconf).

To już wszystkie najważniejsze rzeczy dotyczące dodawania gettextu
do programów. Pełną informację jak zawsze można znaleźć na stronach
info dostarczanych z pakietem GNU gettext.

5 A co ze skryptami w shellu czy perlu lub innych językach ?

Jedyną znaną mi powłoką obsługującą skrypty z tłumaczeniami jest bash
2. W skrypcie basha 2 wpisujemy po prostu:

$ echo $”Hello world!”

Plik PO przygotowujemy natomiast przy użyciu samej powłoki:

$ bash –dump-po-strings skrypt.sh

Generację pliku MO przeprowadzamy już przy użyciu msgfmt z pakietu
GNU gettext. Należy ponadto w skrypcie ustawić zmienną TEXTDOMAIN
na nazwę tekstowej domeny. Niestety nie każdy ma czy chce używać basha
2. Pozostali powinni wykorzystywać w swych skryptach program gettext
zawarty w pakiecie GNU gettext. Przykładowe wywołanie to:

$ gettext –domain=TEXTDOMAIN “Hello world!”

Mogę na marginesie dodać, że dzięki tej metodzie skrypty startowe w
PLD GNU/Linuxie potrafią komunikować się z użytkownikiem m.in. w języku
polskim ! Podobna sytuacja jest w perlu – wystarczy ściągnąć dodatkowy
moduł z archiwum CPAN dodający funkcję gettext. Z gettextu można również
korzystać pisząc programy w innych, nie wspomnianych tutaj językach
programowania np. w pascalu (FreePascal Compilator), a także pod innymi
niż Unixowe systemami – np. pod DOSem (dzięki źródłom biblioteki libintl).

6 Problemy z GNU gettextem.

Niestety gettext nie jest rozwiązaniem idealnym. Jest to narzędzie
dość ograniczone. Podstawowym problemem jest odmiana czy liczba mnoga.
W języku angielskim kilka plików to po prostu ,,files”. W języku
polskim sytuacja się zmienia zależnie od ilości plików (2,3,4 pliki;
5-21 plików itd), natomiast GNU gettext nie pozwala na tłumaczenie
zależne od np. ilości plików. Problem ten nie został rozwiązany do
dnia dzisiejszego (osobom zainteresowanym polecam kod źródłowy programu
lftp, w którym gettext został uzupełniony o częściowe rozwiązanie
umożliwiające tłumaczenie z uwzględnieniem odmian itp.).

7 Na zakończenie.

Mimo licznych ograniczeń GNU gettext jest ważnym narzędziem przybliżającym
środowisko Linuxa ludziom z poza grona osób znających język angielski
– np. sporej liczbie użytkowników polskiej wersji Windows 8-). Zapraszam
wszystkich chętnych do tlumaczenia plików PO w ramach GNU Translation
Project oraz do dodawania obsługi gettexu do już istniejących programów.

8 Adresy.

* GNU Translation Project
http://www.iro.umontreal.ca/~pinard/po/HTML/
http://www.iro.umontreal.ca/contrib/po/

* Polski ,,oddział” GNU Translation Project
http://www.ceti.com.pl/~kravietz/gnu/gnu_tp.html

* Repozytorium CVS PL GNU TP
http://cvsweb.pld.org.pl/index.cgi/i18n/

* Słownik informatyczny, zasady tłumaczenia tekstów informatycznych
http://venus.ci.uw.edu.pl/~milek/

9. Przykłady