editing po files by hand

Dunno if this is actually of any help to anybody, but I was bored on an
airplane 2 days ago and threw this together.

# Adam

Adam Weinberger
adamw@magnesium.net || adamw@FreeBSD.org
adamw@vectors.cx    ||   adamw@gnome.org

Tools like GTranslator are a real time-saver.  They take care of the
boring parts of editing .po files, such as: updating datestamps, making
sure the file is up-to-date, locating errors, and identifying
fuzzy/untranslated strings. However, they all have various dependencies
(i.e., Gtk+-2, an X server, etc.) that make their use infeasible in many
situations.  Although the concepts are perhaps more technically advanced
than may interest casual translators, knowing how to hack .po files
using only a terminal window is an invaluable skill, and can help avoid
some royal PITA situations.

This document will cover useful command-line solutions in decreasing
order of frequency of use.  No matter what, all .po file-related
operations will require some cvs(1) commands; translations are updated
frequently (or should be, anyway); and translations are only created
once.  Thus, they will be covered in that order.

The examples given are for a fictional program "gaksasa," a fictional
language code "gg," which is a pirate locale, and use of vi(1) is


Assuming a named cvs(1) account (substitute an anonymous account if need
be), a fresh copy of the sources to a program can be obtained with:

$ cvs -d :pserver:yourname@cvs.gnome.org:/cvs/gnome -z3 checkout gaksasa

Previously checked out sources can be brought up-to-date without
specifying the CVSROOT:

$ cd gaksasa/
$ cvs -z3 update

Once the changes have been made (AND VERIFIED WITH msfgmt -cv), the
changes are written to the repository with:

$ cvs commit

Commit commands, like the update command, are recursive.  So if you have
edited files in multiple directories, running "cvs commit" from the root
of the source tree will catch them all.  You can specify individual
files instead, if you wish.

To add a new files, use the "cvs add" command before you commit.  Add
commands are not recursive:

$ cvs add new_file
$ cvs commit


In .po files, "msgid" is the source string, and "msgstr" is the
translated string.  For example:

#: somefile.c:242
msgid "Hi."
msgstr "Y'arrrrr!"

Strings with different singular and plural forms are formatted like

#: somefile.c:42
#, c-format
msgid "You have %d nose. This is good."
msgid_plural "You have %d noses. That's not so good."
msgstr[0] "Thar be %d nose."
msgstr[1] "Avast! Thar be %d noses!"

The singular form is the [0] string, and the plural form is the [1]


Once the sources are up-to-date, the source strings in the .po file need
to be brought up-to-date with the strings present in the application's
source code.  This is achieved with the intltool-update(1) command,
which is part of the intltool package.  intltool-update(1) MUST always
be run from the po/ directory:

$ cd gaksasa/
$ cvs -z3 update
$ cd po/
$ intltool-update gg   (NOTE: use your language code in place of "gg")

Note that intltool-update(1) is only passed the language code, and not
the ".po" extension; if intltool-update(1) is given "gg.po" as an
argument, the program will look for gg.po.po and error out.

Once the .po file has been updated, intltool-update(1) displays how many
strings exist within the file, how many are fuzzy (i.e. have changed),
and how many are untranslated (i.e. are new).

When editing the .po file by hand, the PO-Revision-Date and
Last-Translator header fields should be updated as needed.

To seek out strings that have been changed, search for ", fuzzy" with
the command:

	/, fuzzy

Delete the ", fuzzy" portion of the comment, and then edit the string.
For example, change:

#: somefile.c:123
#, fuzzy
msgid "Pantaloons"
msgstr "pants"


#: somefile.c:123
msgid "Pantaloons"
msgstr "Pantaloonses"

And, change:

#: somefile.c:456
#, fuzzy, c-format
msgid "Do not eat the yellow %s"
msgstr "Watch out where the huskies go"


#: somefile.c:456
#, c-format
msgid "Do not eat the yellow %s"
msgstr "Arrr! The yellow %s be not for eatin'."

Untranslated strings can be located with the command:

	/msgstr.* ""\n\n


It takes one second to test a translation, and application authors do
not take kindly to translators who break their application's build.

Translations are tested with the msgfmt(1) command:

$ msgfmt -cv gg.po

msgfmt(1) returns the same output that intltool-update(1) does.  Ideally,
there should be neither untranslated nor fuzzy strings in a committed
.po file, but at the very least the file should be syntactically

Some translations (for example, gimp/tips) cannot update the .po file
directly.  Instead, they generate a .pot template, which must be merged
with an existing translation.  A template and an existing translation
file are merged with the msgmerge(1) command:

$ msgmerge gg.po template.pot

Note that msgmerge(1) is only used when a .po file already exists.
Creating a new .po file from a template is discussed next.


To create a new translation file, first use intltool-update(1) to create
a blank template (program_name.pot) and then copy it to the proper name.
For example:

$ cd gaksasa/
$ cvs -z3 update
$ cd po/
$ intltool-update --pot
$ cp gaksasa.pot gg.po

If creating an English-based translation, it is unnecessary to translate
each line individually.  By starting with all source strings copied
verbatim into the translated string fields, only the strings which need
changing need be modified.  This can be accomplished easily using the
msgen(1) command, which copies all msgid strings into the msgstr fields:

$ cd gaksasa/
$ cvs -z3 update
$ cd po/
$ intltool-update --pot
$ msgen gaksasa.pot > en_GG.po

When creating a new translation, the headers must be edited
appropriately. Change them from:

# This file is distributed under the same license as the PACKAGE
# package.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2004-01-28 20:01+0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

to something like:

# Pirate translation for gaksasa
# Copyright (C) 2004 Your Name and the GNOME Foundation
# This file is distributed under the same license as the gaksasa
# package.
# Your Name <your@email.org>, 2004
msgid ""
msgstr ""
"Project-Id-Version: gaksasa\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2004-01-28 20:01+0700\n"
"PO-Revision-Date: 2004-05-04 08:56:38-0400\n"
"Last-Translator: Your Name <your@email.org>\n"
"Language-Team: Pirate <piratestuff@somehost.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 2);\n"

The charset must ALWAYS be UTF-8.

Do not forget to "cvs add" the new translation before committing.  A
language code in the ALL_LINGUAS array that does not have a
corresponding .po file will kill the build.

This document was created by Adam Weinberger <adamw@gnome.org>,
maintainer of the Canadian English (en_CA) GNOME Translation Project.

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]