Gary Oberbrunner avatar Gary Oberbrunner committed 9d73745 Merge

Merged in ptomulik/scons, pull request #25: gettext toolset

Comments (0)

Files changed (30)

doc/user/MANIFEST

 factories.xml
 file-removal.xml
 functions.xml
+gettext.xml
 hierarchy.xml
 install.xml
 java.xml

doc/user/gettext.in

+<!--
+
+  __COPYRIGHT__
+
+  Permission is hereby granted, free of charge, to any person obtaining
+  a copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so, subject to
+  the following conditions:
+
+  The above copyright notice and this permission notice shall be included
+  in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+  KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-->
+
+  <para>
+  The &t-link-gettext; toolset supports internationalization and localization
+  of SCons-based projects. Builders provided by &t-link-gettext; automatize
+  generation and updates of translation files. You can manage translations and
+  translation templates similarly to how it's done with autotools.
+  </para>
+
+  <section>
+  <title>Prerequisites</title>
+  <para>
+    To follow examples provided in this chapter set up your operating system to
+    support two or more languages. In following examples we use locales
+    <literal>en_US</literal>, <literal>de_DE</literal>, and
+    <literal>pl_PL</literal>.
+  </para>
+
+  <para>
+    Ensure, that you have <ulink
+    url="http://www.gnu.org/software/gettext/manual/gettext.html">GNU gettext
+    utilities</ulink> installed on your system.
+  </para>
+
+  <para>
+    To edit translation files you may wish to install <ulink
+    url="http://www.poedit.net/">poedit</ulink> editor.
+  </para>
+  </section>
+
+  <section>
+  <title>Simple project</title>
+    <para>
+    Let's start with a very simple project, the "Hello world" program 
+    for example
+    <scons_example name="ex1">
+    <file name="hello.c" printme="1">
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    int main(int argc, char* argv[])
+    {
+      printf("Hello world\n");
+      return 0;
+    }
+    </file>
+    </scons_example>
+
+    Prepare a <filename>SConstruct</filename> to compile the program
+    as usual.
+    <scons_example name="ex2">
+    <file name="SConstruct" printme="1">
+    # SConstruct
+    env = Environment()
+    hello = Program(["hello.c"])
+    </file>
+    </scons_example>
+    </para>
+
+    <para>
+    Now we'll convert the project to a multi-lingual one. If you don't
+    already have <ulink
+    url="http://www.gnu.org/software/gettext/manual/gettext.html">GNU gettext
+    utilities</ulink> installed, install them from your preffered
+    package repository, or download from <ulink
+    url="http://ftp.gnu.org/gnu/gettext/">
+    http://ftp.gnu.org/gnu/gettext/</ulink>. For the purpose of this example,
+    you should have following three locales installed on your system:
+    <literal>en_US</literal>, <literal>de_DE</literal> and
+    <literal>pl_PL</literal>. On debian, for example, you may enable certain
+    locales through <command>dpkg-reconfigure locales</command>.
+    </para>
+
+    <para>
+    First prepare the <filename>hello.c</filename> program for
+    internationalization. Change the previous code so it reads as follows:
+    <scons_example name="ex3">
+    <file name="hello.c" printme="1">
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    #include &lt;libintl.h&gt;
+    #include &lt;locale.h&gt;
+    int main(int argc, char* argv[])
+    {
+      bindtextdomain("hello", "locale");
+      setlocale(LC_ALL, "");
+      textdomain("hello");
+      printf(gettext("Hello world\n"));
+      return 0;
+    }
+    </file>
+    </scons_example>
+    Detailed recipes for such conversion can
+    be found at <ulink
+    url="http://www.gnu.org/software/gettext/manual/gettext.html#Sources">
+    http://www.gnu.org/software/gettext/manual/gettext.html#Sources</ulink>.
+    The <function>gettext("...")</function> has two purposes.
+    First, it marks messages for the <command>xgettext(1)</command> program, which
+    we will use to extract from the sources the messages for localization.
+    Second, it calls the <literal>gettext</literal> library internals to
+    translate the message at runtime.
+    </para>
+
+    <para> 
+    Now we shall instruct SCons how to generate and maintain translation files.
+    For that, use the &b-link-Translate; builder and &b-link-MOFiles; builder.
+    The first one takes source files, extracts internationalized
+    messages from them, creates so-called <literal>POT</literal> file
+    (translation template), and then creates <literal>PO</literal> translation
+    files, one for each requested language. Later, during the development
+    lifecycle, the builder keeps all these files up-to date. The
+    &b-link-MOFiles; builder compiles the <literal>PO</literal> files to binary
+    form. Then install the <literal>MO</literal> files under directory
+    called <filename>locale</filename>.
+    </para>
+
+    <para>  The completed 
+    <filename>SConstruct</filename> is as follows:
+    <scons_example name="ex4">
+    <file name="SConstruct" printme="1">
+    # SConstruct
+    env = Environment( tools = ['default', 'gettext'] )
+    hello = env.Program(["hello.c"])
+    env['XGETTEXTFLAGS'] = [
+      '--package-name=%s' % 'hello',
+      '--package-version=%s' % '1.0',
+    ]
+    po = env.Translate(["pl","en", "de"], ["hello.c"], POAUTOINIT = 1)
+    mo = env.MOFiles(po)
+    InstallAs(["locale/en/LC_MESSAGES/hello.mo"], ["en.mo"])
+    InstallAs(["locale/pl/LC_MESSAGES/hello.mo"], ["pl.mo"])
+    InstallAs(["locale/de/LC_MESSAGES/hello.mo"], ["de.mo"])
+    </file>
+    </scons_example>
+    </para>
+    <para>
+    Generate the translation files with <command>scons po-update</command>.
+    You should see the output from SCons simillar to this:
+    <screen>
+    user@host:$ scons po-update
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    Entering '/home/ptomulik/projects/tmp'
+    xgettext --package-name=hello --package-version=1.0 -o - hello.c
+    Leaving '/home/ptomulik/projects/tmp'
+    Writting 'messages.pot' (new file)
+    msginit --no-translator -l pl -i messages.pot -o pl.po
+    Created pl.po.
+    msginit --no-translator -l en -i messages.pot -o en.po
+    Created en.po.
+    msginit --no-translator -l de -i messages.pot -o de.po
+    Created de.po.
+    scons: done building targets.
+    </screen>
+    </para>
+
+    <para>
+    If everything is right, you should see following new files.
+    <screen>
+    user@host:$ ls *.po*
+    de.po  en.po  messages.pot  pl.po
+    </screen>
+    </para>
+
+    <para>
+    Open <filename>en.po</filename> in <command>poedit</command> and provide
+    the English translation to message <literal>"Hello world\n"</literal>. Do the
+    same for <filename>de.po</filename> (deutsch) and
+    <filename>pl.po</filename> (polish). Let the translations be, for example:
+    <itemizedlist>
+      <listitem><para>
+        <literal>en: "Welcome to beautiful world!\n"</literal>
+      </para></listitem>
+      <listitem><para>
+        <literal>de: "Hallo Welt!\n"</literal>
+      </para></listitem>
+      <listitem><para>
+        <literal>pl: "Witaj swiecie!\n"</literal>
+      </para></listitem>
+    </itemizedlist>
+    </para>
+    <para>
+    Now compile the project by executing <command>scons</command>. The
+    output should be similar to this:
+    <screen>
+    user@host:$ scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    msgfmt -c -o de.mo de.po
+    msgfmt -c -o en.mo en.po
+    gcc -o hello.o -c hello.c
+    gcc -o hello hello.o
+    Install file: "de.mo" as "locale/de/LC_MESSAGES/hello.mo"
+    Install file: "en.mo" as "locale/en/LC_MESSAGES/hello.mo"
+    msgfmt -c -o pl.mo pl.po
+    Install file: "pl.mo" as "locale/pl/LC_MESSAGES/hello.mo"
+    scons: done building targets.
+    </screen>
+    SCons automatically compiled the <literal>PO</literal> files to binary format
+    <literal>MO</literal>, and the <literal>InstallAs</literal> lines installed
+    these files under <filename>locale</filename> folder.
+    </para>
+    <para>
+    Your program should be now ready. You may try it as follows (linux):
+    <screen>
+    user@host:$ LANG=en_US.UTF-8 ./hello
+    Welcome to beautiful world
+    </screen>
+    <screen>
+    user@host:$ LANG=de_DE.UTF-8 ./hello
+    Hallo Welt
+    </screen>
+    <screen>
+    user@host:$ LANG=pl_PL.UTF-8 ./hello
+    Witaj swiecie
+    </screen>
+    </para>
+    <para>
+    To demonstrate the further life of translation files, let's change Polish
+    translation (<command>poedit pl.po</command>) to <literal>"Witaj drogi
+    swiecie\n"</literal>. Run <command>scons</command> to see how scons
+    reacts to this
+    <screen>
+    user@host:$scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    msgfmt -c -o pl.mo pl.po
+    Install file: "pl.mo" as "locale/pl/LC_MESSAGES/hello.mo"
+    scons: done building targets.
+    </screen>
+    </para>
+    <para>
+    Now, open <filename>hello.c</filename> and add another one
+    <literal>printf</literal> line with new message.
+    <scons_example name="ex5">
+    <file name="hello.c" printme="1">
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    #include &lt;libintl.h&gt;
+    #include &lt;locale.h&gt;
+    int main(int argc, char* argv[])
+    {
+      bindtextdomain("hello", "locale");
+      setlocale(LC_ALL, "");
+      textdomain("hello");
+      printf(gettext("Hello world\n"));
+      printf(gettext("and good bye\n"));
+      return 0;
+    }
+    </file>
+    </scons_example>
+    </para>
+    <para>
+    Compile project with <command>scons</command>. This time, the
+    <command>msgmerge(1)</command> program is used by SCons to update
+    <literal>PO</literal> file. The output from compilation is like:
+    <screen>
+    user@host:$scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    Entering '/home/ptomulik/projects/tmp'
+    xgettext --package-name=hello --package-version=1.0 -o - hello.c
+    Leaving '/home/ptomulik/projects/tmp'
+    Writting 'messages.pot' (messages in file were outdated)
+    msgmerge --update de.po messages.pot
+    ... done.
+    msgfmt -c -o de.mo de.po
+    msgmerge --update en.po messages.pot
+    ... done.
+    msgfmt -c -o en.mo en.po
+    gcc -o hello.o -c hello.c
+    gcc -o hello hello.o
+    Install file: "de.mo" as "locale/de/LC_MESSAGES/hello.mo"
+    Install file: "en.mo" as "locale/en/LC_MESSAGES/hello.mo"
+    msgmerge --update pl.po messages.pot
+    ... done.
+    msgfmt -c -o pl.mo pl.po
+    Install file: "pl.mo" as "locale/pl/LC_MESSAGES/hello.mo"
+    scons: done building targets.
+    </screen>
+    </para>
+    <para>
+    The next example demonstrates what happens if we change the source code
+    in such way that the internationalized messages do not change. The answer
+    is that none of translation files (<literal>POT</literal>,
+    <literal>PO</literal>) are touched (i.e. no content changes, no
+    creation/modification time changed and so on). Let's append another
+    line to the program (after the last printf), so its code becomes:
+    <scons_example name="ex6">
+    <file name="hello.c" printme="1">
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    #include &lt;libintl.h&gt;
+    #include &lt;locale.h&gt;
+    int main(int argc, char* argv[])
+    {
+      bindtextdomain("hello", "locale");
+      setlocale(LC_ALL, "");
+      textdomain("hello");
+      printf(gettext("Hello world\n"));
+      printf(gettext("and good bye\n"));
+      printf("----------------\n");
+      return a;
+    }
+    </file>
+    </scons_example>
+    Compile the project. You'll see on your screen
+    <screen>
+    user@host:$scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    Entering '/home/ptomulik/projects/tmp'
+    xgettext --package-name=hello --package-version=1.0 -o - hello.c
+    Leaving '/home/ptomulik/projects/tmp'
+    Not writting 'messages.pot' (messages in file found to be up-to-date)
+    gcc -o hello.o -c hello.c
+    gcc -o hello hello.o
+    scons: done building targets.
+    </screen>
+    As you see, the internationalized messages ditn't change, so the
+    <literal>POT</literal> and the rest of translation files have not
+    even been touched.
+    </para>
+  </section>

doc/user/gettext.xml

+<!--
+
+  __COPYRIGHT__
+
+  Permission is hereby granted, free of charge, to any person obtaining
+  a copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so, subject to
+  the following conditions:
+
+  The above copyright notice and this permission notice shall be included
+  in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+  KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-->
+
+  <para>
+  The &t-link-gettext; toolset supports internationalization and localization
+  of SCons-based projects. Builders provided by &t-link-gettext; automatize
+  generation and updates of translation files. You can manage translations and
+  translation templates similarly to how it's done with autotools.
+  </para>
+
+  <section>
+  <title>Prerequisites</title>
+  <para>
+    To follow examples provided in this chapter set up your operating system to
+    support two or more languages. In following examples we use locales
+    <literal>en_US</literal>, <literal>de_DE</literal>, and
+    <literal>pl_PL</literal>.
+  </para>
+
+  <para>
+    Ensure, that you have <ulink url="http://www.gnu.org/software/gettext/manual/gettext.html">GNU gettext
+    utilities</ulink> installed on your system.
+  </para>
+
+  <para>
+    To edit translation files you may wish to install <ulink url="http://www.poedit.net/">poedit</ulink> editor.
+  </para>
+  </section>
+
+  <section>
+  <title>Simple project</title>
+    <para>
+    Let's start with a very simple project, the "Hello world" program 
+    for example
+    <programlisting>
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    int main(int argc, char* argv[])
+    {
+      printf("Hello world\n");
+      return 0;
+    }
+    </programlisting>
+
+    Prepare a <filename>SConstruct</filename> to compile the program
+    as usual.
+    <programlisting>
+    # SConstruct
+    env = Environment()
+    hello = Program(["hello.c"])
+    </programlisting>
+    </para>
+
+    <para>
+    Now we'll convert the project to a multi-lingual one. If you don't
+    already have <ulink url="http://www.gnu.org/software/gettext/manual/gettext.html">GNU gettext
+    utilities</ulink> installed, install them from your preffered
+    package repository, or download from <ulink url="http://ftp.gnu.org/gnu/gettext/">
+    http://ftp.gnu.org/gnu/gettext/</ulink>. For the purpose of this example,
+    you should have following three locales installed on your system:
+    <literal>en_US</literal>, <literal>de_DE</literal> and
+    <literal>pl_PL</literal>. On debian, for example, you may enable certain
+    locales through <command>dpkg-reconfigure locales</command>.
+    </para>
+
+    <para>
+    First prepare the <filename>hello.c</filename> program for
+    internationalization. Change the previous code so it reads as follows:
+    <programlisting>
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    #include &lt;libintl.h&gt;
+    #include &lt;locale.h&gt;
+    int main(int argc, char* argv[])
+    {
+      bindtextdomain("hello", "locale");
+      setlocale(LC_ALL, "");
+      textdomain("hello");
+      printf(gettext("Hello world\n"));
+      return 0;
+    }
+    </programlisting>
+    Detailed recipes for such conversion can
+    be found at <ulink url="http://www.gnu.org/software/gettext/manual/gettext.html#Sources">
+    http://www.gnu.org/software/gettext/manual/gettext.html#Sources</ulink>.
+    The <function>gettext("...")</function> has two purposes.
+    First, it marks messages for the <command>xgettext(1)</command> program, which
+    we will use to extract from the sources the messages for localization.
+    Second, it calls the <literal>gettext</literal> library internals to
+    translate the message at runtime.
+    </para>
+
+    <para> 
+    Now we shall instruct SCons how to generate and maintain translation files.
+    For that, use the &b-link-Translate; builder and &b-link-MOFiles; builder.
+    The first one takes source files, extracts internationalized
+    messages from them, creates so-called <literal>POT</literal> file
+    (translation template), and then creates <literal>PO</literal> translation
+    files, one for each requested language. Later, during the development
+    lifecycle, the builder keeps all these files up-to date. The
+    &b-link-MOFiles; builder compiles the <literal>PO</literal> files to binary
+    form. Then install the <literal>MO</literal> files under directory
+    called <filename>locale</filename>.
+    </para>
+
+    <para>  The completed 
+    <filename>SConstruct</filename> is as follows:
+    <programlisting>
+    # SConstruct
+    env = Environment( tools = ['default', 'gettext'] )
+    hello = env.Program(["hello.c"])
+    env['XGETTEXTFLAGS'] = [
+      '--package-name=%s' % 'hello',
+      '--package-version=%s' % '1.0',
+    ]
+    po = env.Translate(["pl","en", "de"], ["hello.c"], POAUTOINIT = 1)
+    mo = env.MOFiles(po)
+    InstallAs(["locale/en/LC_MESSAGES/hello.mo"], ["en.mo"])
+    InstallAs(["locale/pl/LC_MESSAGES/hello.mo"], ["pl.mo"])
+    InstallAs(["locale/de/LC_MESSAGES/hello.mo"], ["de.mo"])
+    </programlisting>
+    </para>
+    <para>
+    Generate the translation files with <command>scons po-update</command>.
+    You should see the output from SCons simillar to this:
+    <screen>
+    user@host:$ scons po-update
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    Entering '/home/ptomulik/projects/tmp'
+    xgettext --package-name=hello --package-version=1.0 -o - hello.c
+    Leaving '/home/ptomulik/projects/tmp'
+    Writting 'messages.pot' (new file)
+    msginit --no-translator -l pl -i messages.pot -o pl.po
+    Created pl.po.
+    msginit --no-translator -l en -i messages.pot -o en.po
+    Created en.po.
+    msginit --no-translator -l de -i messages.pot -o de.po
+    Created de.po.
+    scons: done building targets.
+    </screen>
+    </para>
+
+    <para>
+    If everything is right, you should see following new files.
+    <screen>
+    user@host:$ ls *.po*
+    de.po  en.po  messages.pot  pl.po
+    </screen>
+    </para>
+
+    <para>
+    Open <filename>en.po</filename> in <command>poedit</command> and provide
+    the English translation to message <literal>"Hello world\n"</literal>. Do the
+    same for <filename>de.po</filename> (deutsch) and
+    <filename>pl.po</filename> (polish). Let the translations be, for example:
+    <itemizedlist>
+      <listitem><para>
+        <literal>en: "Welcome to beautiful world!\n"</literal>
+      </para></listitem>
+      <listitem><para>
+        <literal>de: "Hallo Welt!\n"</literal>
+      </para></listitem>
+      <listitem><para>
+        <literal>pl: "Witaj swiecie!\n"</literal>
+      </para></listitem>
+    </itemizedlist>
+    </para>
+    <para>
+    Now compile the project by executing <command>scons</command>. The
+    output should be similar to this:
+    <screen>
+    user@host:$ scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    msgfmt -c -o de.mo de.po
+    msgfmt -c -o en.mo en.po
+    gcc -o hello.o -c hello.c
+    gcc -o hello hello.o
+    Install file: "de.mo" as "locale/de/LC_MESSAGES/hello.mo"
+    Install file: "en.mo" as "locale/en/LC_MESSAGES/hello.mo"
+    msgfmt -c -o pl.mo pl.po
+    Install file: "pl.mo" as "locale/pl/LC_MESSAGES/hello.mo"
+    scons: done building targets.
+    </screen>
+    SCons automatically compiled the <literal>PO</literal> files to binary format
+    <literal>MO</literal>, and the <literal>InstallAs</literal> lines installed
+    these files under <filename>locale</filename> folder.
+    </para>
+    <para>
+    Your program should be now ready. You may try it as follows (linux):
+    <screen>
+    user@host:$ LANG=en_US.UTF-8 ./hello
+    Welcome to beautiful world
+    </screen>
+    <screen>
+    user@host:$ LANG=de_DE.UTF-8 ./hello
+    Hallo Welt
+    </screen>
+    <screen>
+    user@host:$ LANG=pl_PL.UTF-8 ./hello
+    Witaj swiecie
+    </screen>
+    </para>
+    <para>
+    To demonstrate the further life of translation files, let's change Polish
+    translation (<command>poedit pl.po</command>) to <literal>"Witaj drogi
+    swiecie\n"</literal>. Run <command>scons</command> to see how scons
+    reacts to this
+    <screen>
+    user@host:$scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    msgfmt -c -o pl.mo pl.po
+    Install file: "pl.mo" as "locale/pl/LC_MESSAGES/hello.mo"
+    scons: done building targets.
+    </screen>
+    </para>
+    <para>
+    Now, open <filename>hello.c</filename> and add another one
+    <literal>printf</literal> line with new message.
+    <programlisting>
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    #include &lt;libintl.h&gt;
+    #include &lt;locale.h&gt;
+    int main(int argc, char* argv[])
+    {
+      bindtextdomain("hello", "locale");
+      setlocale(LC_ALL, "");
+      textdomain("hello");
+      printf(gettext("Hello world\n"));
+      printf(gettext("and good bye\n"));
+      return 0;
+    }
+    </programlisting>
+    </para>
+    <para>
+    Compile project with <command>scons</command>. This time, the
+    <command>msgmerge(1)</command> program is used by SCons to update
+    <literal>PO</literal> file. The output from compilation is like:
+    <screen>
+    user@host:$scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    Entering '/home/ptomulik/projects/tmp'
+    xgettext --package-name=hello --package-version=1.0 -o - hello.c
+    Leaving '/home/ptomulik/projects/tmp'
+    Writting 'messages.pot' (messages in file were outdated)
+    msgmerge --update de.po messages.pot
+    ... done.
+    msgfmt -c -o de.mo de.po
+    msgmerge --update en.po messages.pot
+    ... done.
+    msgfmt -c -o en.mo en.po
+    gcc -o hello.o -c hello.c
+    gcc -o hello hello.o
+    Install file: "de.mo" as "locale/de/LC_MESSAGES/hello.mo"
+    Install file: "en.mo" as "locale/en/LC_MESSAGES/hello.mo"
+    msgmerge --update pl.po messages.pot
+    ... done.
+    msgfmt -c -o pl.mo pl.po
+    Install file: "pl.mo" as "locale/pl/LC_MESSAGES/hello.mo"
+    scons: done building targets.
+    </screen>
+    </para>
+    <para>
+    The next example demonstrates what happens if we change the source code
+    in such way that the internationalized messages do not change. The answer
+    is that none of translation files (<literal>POT</literal>,
+    <literal>PO</literal>) are touched (i.e. no content changes, no
+    creation/modification time changed and so on). Let's append another
+    line to the program (after the last printf), so its code becomes:
+    <programlisting>
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    #include &lt;libintl.h&gt;
+    #include &lt;locale.h&gt;
+    int main(int argc, char* argv[])
+    {
+      bindtextdomain("hello", "locale");
+      setlocale(LC_ALL, "");
+      textdomain("hello");
+      printf(gettext("Hello world\n"));
+      printf(gettext("and good bye\n"));
+      printf("----------------\n");
+      return a;
+    }
+    </programlisting>
+    Compile the project. You'll see on your screen
+    <screen>
+    user@host:$scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    Entering '/home/ptomulik/projects/tmp'
+    xgettext --package-name=hello --package-version=1.0 -o - hello.c
+    Leaving '/home/ptomulik/projects/tmp'
+    Not writting 'messages.pot' (messages in file found to be up-to-date)
+    gcc -o hello.o -c hello.c
+    gcc -o hello hello.o
+    scons: done building targets.
+    </screen>
+    As you see, the internationalized messages ditn't change, so the
+    <literal>POT</literal> and the rest of translation files have not
+    even been touched.
+    </para>
+  </section>
     <!ENTITY factories SYSTEM "factories.xml">
     <!ENTITY file-removal SYSTEM "file-removal.xml">
     <!ENTITY functions SYSTEM "functions.xml">
+    <!ENTITY gettext SYSTEM "gettext.xml">
     <!ENTITY hierarchy SYSTEM "hierarchy.xml">
     <!ENTITY java SYSTEM "java.xml">
     <!ENTITY install SYSTEM "install.xml">
     &variants;
   </chapter>
 
+  <chapter id="chap-gettext">
+    <title>Internationalization and localization with gettext</title>
+    &gettext;
+  </chapter>
+
   <!--
 
   <chapter id="chap-builders-built-in">

doc/user/main.xml

     <!ENTITY factories SYSTEM "factories.xml">
     <!ENTITY file-removal SYSTEM "file-removal.xml">
     <!ENTITY functions SYSTEM "functions.xml">
+    <!ENTITY gettext SYSTEM "gettext.xml">
     <!ENTITY hierarchy SYSTEM "hierarchy.xml">
     <!ENTITY java SYSTEM "java.xml">
     <!ENTITY install SYSTEM "install.xml">
     &variants;
   </chapter>
 
+  <chapter id="chap-gettext">
+    <title>Internationalization and localization with gettext</title>
+    &gettext;
+  </chapter>
+
   <!--
 
   <chapter id="chap-builders-built-in">

doc/user/troubleshoot.xml

                          '.SPP',
                          '.sx'],
         'CXX': '$CC',
-        'CXXCOM': '$CXX $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $CXXFLAGS $CCFLAGS $_CCCOMCOM',
+        'CXXCOM': '${TEMPFILE("$CXX $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $CXXFLAGS $CCFLAGS $_CCCOMCOM")}',
         'CXXFILESUFFIX': '.cc',
         'CXXFLAGS': ['$(', '/TP', '$)'],
         'DSUFFIXES': ['.d'],
         'SHCCFLAGS': ['$CCFLAGS'],
         'SHCFLAGS': ['$CFLAGS'],
         'SHCXX': '$CXX',
-        'SHCXXCOM': '$SHCXX $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $SHCXXFLAGS $SHCCFLAGS $_CCCOMCOM',
+        'SHCXXCOM': '${TEMPFILE("$SHCXX $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $SHCXXFLAGS $SHCCFLAGS $_CCCOMCOM")}',
         'SHCXXFLAGS': ['$CXXFLAGS'],
         'SHELL': None,
         'SHLIBPREFIX': '',
                             Change Log
 
 RELEASE 2.X.X - 
+  From Paweł Tomulik:
+    - Added gettext toolset
+    - Fixed FindSourceFiles to find final sources (leaf nodes).
 
   From Greg Ward:
     - Allow Node objects in Java path (#2825)

src/engine/MANIFEST-xml.in

 SCons/Tool/tlib.xml
 SCons/Tool/yacc.xml
 SCons/Tool/zip.xml
+SCons/Tool/gettext.xml
+SCons/Tool/msgfmt.xml
+SCons/Tool/msginit.xml
+SCons/Tool/msgmerge.xml
+SCons/Tool/xgettext.xml

src/engine/MANIFEST.in

 SCons/Variables/PackageVariable.py
 SCons/Variables/PathVariable.py
 SCons/Warnings.py
+SCons/Tool/GettextCommon.py
+SCons/Tool/gettext.py
+SCons/Tool/msgfmt.py
+SCons/Tool/msginit.py
+SCons/Tool/msgmerge.py
+SCons/Tool/xgettext.py

src/engine/SCons/Environment.py

                     sources.append(s)
         build_source(node.all_children())
 
-    # THIS CODE APPEARS TO HAVE NO EFFECT
-    #    # get the final srcnode for all nodes, this means stripping any
-    #    # attached build node by calling the srcnode function
-    #    for file in sources:
-    #        srcnode = file.srcnode()
-    #        while srcnode != file.srcnode():
-    #            srcnode = file.srcnode()
-
+        def final_source(node):
+            while (node != node.srcnode()):
+              node = node.srcnode()
+            return node
+        sources = map( final_source, sources );
         # remove duplicates
         return list(set(sources))
 

src/engine/SCons/Tool/GettextCommon.py

+"""SCons.Tool.GettextCommon module
+
+Used by several tools of `gettext` toolset.
+"""
+
+# __COPYRIGHT__
+# 
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.Warnings
+import re
+
+#############################################################################
+class XgettextToolWarning(SCons.Warnings.Warning): pass
+class XgettextNotFound(XgettextToolWarning): pass
+class MsginitToolWarning(SCons.Warnings.Warning): pass
+class MsginitNotFound(MsginitToolWarning): pass
+class MsgmergeToolWarning(SCons.Warnings.Warning): pass
+class MsgmergeNotFound(MsgmergeToolWarning): pass
+class MsgfmtToolWarning(SCons.Warnings.Warning): pass
+class MsgfmtNotFound(MsgfmtToolWarning): pass
+#############################################################################
+SCons.Warnings.enableWarningClass(XgettextToolWarning)
+SCons.Warnings.enableWarningClass(XgettextNotFound)
+SCons.Warnings.enableWarningClass(MsginitToolWarning)
+SCons.Warnings.enableWarningClass(MsginitNotFound)
+SCons.Warnings.enableWarningClass(MsgmergeToolWarning)
+SCons.Warnings.enableWarningClass(MsgmergeNotFound)
+SCons.Warnings.enableWarningClass(MsgfmtToolWarning)
+SCons.Warnings.enableWarningClass(MsgfmtNotFound)
+#############################################################################
+
+#############################################################################
+class _POTargetFactory(object):
+  """ A factory of `PO` target files.
+  
+  Factory defaults differ from these of `SCons.Node.FS.FS`.  We set `precious`
+  (this is required by builders and actions gettext) and `noclean` flags by
+  default for all produced nodes.
+  """
+  def __init__( self, env, nodefault = True, alias = None, precious = True
+              , noclean = True ):
+    """ Object constructor.
+
+    **Arguments**
+
+        - *env* (`SCons.Environment.Environment`)
+        - *nodefault* (`boolean`) - if `True`, produced nodes will be ignored
+          from default target `'.'`
+        - *alias* (`string`) - if provided, produced nodes will be automatically
+          added to this alias, and alias will be set as `AlwaysBuild`
+        - *precious* (`boolean`) - if `True`, the produced nodes will be set as
+          `Precious`.
+        - *noclen* (`boolean`) - if `True`, the produced nodes will be excluded
+          from `Clean`.
+    """
+    self.env = env
+    self.alias = alias
+    self.precious = precious
+    self.noclean = noclean
+    self.nodefault = nodefault
+
+  def _create_node(self, name, factory, directory = None, create = 1):
+    """ Create node, and set it up to factory settings. """
+    import SCons.Util
+    node = factory(name, directory, create)
+    node.set_noclean(self.noclean)
+    node.set_precious(self.precious)
+    if self.nodefault:
+      self.env.Ignore('.', node)
+    if self.alias:
+      self.env.AlwaysBuild(self.env.Alias(self.alias, node))
+    return node
+
+  def Entry(self, name, directory = None, create = 1):
+    """ Create `SCons.Node.FS.Entry` """
+    return self._create_node(name, self.env.fs.Entry, directory, create)
+
+  def File(self, name, directory = None, create = 1):
+    """ Create `SCons.Node.FS.File` """
+    return self._create_node(name, self.env.fs.File, directory, create)
+#############################################################################
+
+#############################################################################
+_re_comment = re.compile(r'(#[^\n\r]+)$', re.M)
+_re_lang = re.compile(r'([a-zA-Z0-9_]+)', re.M)
+#############################################################################
+def _read_linguas_from_files(env, linguas_files = None):
+  """ Parse `LINGUAS` file and return list of extracted languages """
+  import SCons.Util
+  import SCons.Environment
+  global _re_comment
+  global _re_lang
+  if not SCons.Util.is_List(linguas_files) \
+  and not SCons.Util.is_String(linguas_files) \
+  and not isinstance(linguas_files, SCons.Node.FS.Base) \
+  and linguas_files:
+    # If, linguas_files==True or such, then read 'LINGUAS' file.
+    linguas_files = [ 'LINGUAS' ]
+  if linguas_files is None:
+    return []  
+  fnodes = env.arg2nodes(linguas_files)
+  linguas = []
+  for fnode in fnodes:
+    contents =  _re_comment.sub("", fnode.get_text_contents())
+    ls = [ l for l in _re_lang.findall(contents) if l ]
+    linguas.extend(ls)
+  return linguas 
+#############################################################################
+
+#############################################################################
+from SCons.Builder import BuilderBase
+#############################################################################
+class _POFileBuilder(BuilderBase):
+  """ `PO` file builder.
+
+  This is multi-target single-source builder. In typical situation the source
+  is single `POT` file, e.g. `messages.pot`, and there are multiple `PO`
+  targets to be updated from this `POT`. We must run
+  `SCons.Builder.BuilderBase._execute()` separatelly for each target to track
+  dependencies separatelly for each target file.
+  
+  **NOTE**: if we call `SCons.Builder.BuilderBase._execute(.., target, ...)`
+  with target being list of all targets, all targets would be rebuilt each time
+  one of the targets from this list is missing. This would happen, for example,
+  when new language `ll` enters `LINGUAS_FILE` (at this moment there is no
+  `ll.po` file yet). To avoid this, we override
+  `SCons.Builder.BuilerBase._execute()` and call it separatelly for each
+  target. Here we also append to the target list the languages read from
+  `LINGUAS_FILE`.
+  """
+  #
+  #* The argument for overriding _execute(): We must use environment with
+  #  builder overrides applied (see BuilderBase.__init__(). Here it comes for
+  #  free.
+  #* The argument against using 'emitter': The emitter is called too late
+  #  by BuilderBase._execute(). If user calls, for example:
+  #  
+  #    env.POUpdate(LINGUAS_FILE = 'LINGUAS')
+  #
+  #  the builder throws error, because it is called with target=None,
+  #  source=None and is trying to "generate" sources or target list first.
+  #  If user calls
+  #
+  #    env.POUpdate(['foo', 'baz'], LINGUAS_FILE = 'LINGUAS')
+  #
+  #  the env.BuilderWrapper() calls our builder with target=None,
+  #  source=['foo', 'baz']. The BuilderBase._execute() then splits execution
+  #  and execute iterativelly (recursion) self._execute(None, source[i]). 
+  #  After that it calls emitter (which is quite too late). The emitter is
+  #  also called in each iteration, what makes things yet worse.
+  def __init__(self, env, **kw):
+    if not 'suffix' in kw:
+       kw['suffix'] = '$POSUFFIX'
+    if not 'src_suffix' in kw:
+       kw['src_suffix'] = '$POTSUFFIX'
+    if not 'src_builder' in kw:
+       kw['src_builder'] = '_POTUpdateBuilder'
+    if not 'single_source' in kw:
+       kw['single_source'] = True
+    alias = None
+    if 'target_alias' in kw:
+       alias = kw['target_alias']
+       del kw['target_alias']
+    if not 'target_factory' in kw:
+       kw['target_factory'] = _POTargetFactory(env, alias=alias).File
+    BuilderBase.__init__(self, **kw)
+
+  def _execute(self, env, target, source, *args, **kw):
+    """ Execute builder's actions.
+    
+    Here we append to `target` the languages read from `$LINGUAS_FILE` and 
+    apply `SCons.Builder.BuilderBase._execute()` separatelly to each target.
+    The arguments and return value are same as for
+    `SCons.Builder.BuilderBase._execute()`. 
+    """
+    import SCons.Util
+    import SCons.Node
+    linguas_files = None
+    if env.has_key('LINGUAS_FILE') and env['LINGUAS_FILE']:
+      linguas_files = env['LINGUAS_FILE']
+      # This prevents endless recursion loop (we'll be invoked once for 
+      # each target appended here, we must not extend the list again).
+      env['LINGUAS_FILE'] = None
+      linguas = _read_linguas_from_files(env,linguas_files)
+      if SCons.Util.is_List(target):
+        target.extend(linguas)
+      elif target is not None:
+        target = [target] + linguas
+      else:
+        target = linguas
+    if not target:
+      # Let the SCons.BuilderBase to handle this patologic situation
+      return BuilderBase._execute( self, env, target, source, *args, **kw)
+    # The rest is ours
+    if not SCons.Util.is_List(target):
+      target = [ target ]
+    result = []
+    for tgt in target:
+      r = BuilderBase._execute( self, env, [tgt], source, *args, **kw)
+      result.extend(r)
+    if linguas_files is not None:
+      env['LINGUAS_FILE'] = linguas_files
+    return SCons.Node.NodeList(result)
+#############################################################################
+
+import SCons.Environment
+#############################################################################
+def _translate(env, target=[], source=SCons.Environment._null, *args, **kw):
+  """ Function for `Translate()` pseudo-builder """
+  pot = env.POTUpdate(None, source, *args, **kw)
+  po = env.POUpdate(target, pot, *args, **kw)
+  return po
+#############################################################################
+
+#############################################################################
+class RPaths(object):
+  """ Callable object, which returns pathnames relative to SCons current
+  working directory.
+
+  It seems like `SCons.Node.FS.Base.get_path()` returns absolute paths
+  for nodes that are outside of current working directory (`env.fs.getcwd()`).
+  Here, we often have `SConscript`, `POT` and `PO` files within `po/`
+  directory and source files (e.g. `*.c`) outside of it. When generating `POT`
+  template file, references to source files are written to `POT` template, so
+  a translator may later quickly jump to appropriate source file and line from
+  its `PO` editor (e.g. `poedit`).  Relative paths in  `PO` file are usually
+  interpreted by `PO` editor as paths relative to the place, where `PO` file
+  lives. The absolute paths would make resultant `POT` file nonportable, as
+  the references would be correct only on the machine, where `POT` file was
+  recently re-created. For such reason, we need a function, which always
+  returns relative paths. This is the purpose of `RPaths` callable object.
+
+  The `__call__` method returns paths relative to current woking directory, but
+  we assume, that *xgettext(1)* is run from the directory, where target file is
+  going to be created.
+
+  Note, that this may not work for files distributed over several hosts or
+  across different drives on windows. We assume here, that single local
+  filesystem holds both source files and target `POT` templates.
+
+  Intended use of `RPaths` - in `xgettext.py`::
+
+    def generate(env):
+        from GettextCommon import RPaths
+        ...
+        sources = '$( ${_concat( "", SOURCES, "", __env__, XgettextRPaths, TARGET, SOURCES)} $)'
+        env.Append(
+          ...
+          XGETTEXTCOM = 'XGETTEXT ... ' + sources,
+          ...
+          XgettextRPaths = RPaths(env)
+        )
+  """
+  # NOTE: This callable object returns pathnames of dirs/files relative to
+  # current working directory. The pathname remains relative also for entries
+  # that are outside of current working directory (node, that
+  # SCons.Node.FS.File and siblings return absolute path in such case). For
+  # simplicity we compute path relative to current working directory, this
+  # seems be enough for our purposes (don't need TARGET variable and
+  # SCons.Defaults.Variable_Caller stuff).
+  
+  def __init__(self, env):
+    """ Initialize `RPaths` callable object.
+
+      **Arguments**:
+
+        - *env* - a `SCons.Environment.Environment` object, defines *current
+          working dir*.
+    """
+    self.env = env
+
+  # FIXME: I'm not sure, how it should be implemented (what the *args are in
+  # general, what is **kw).
+  def __call__(self, nodes, *args, **kw):
+    """ Return nodes' paths (strings) relative to current working directory. 
+    
+      **Arguments**:
+
+        - *nodes* ([`SCons.Node.FS.Base`]) - list of nodes.
+        - *args* -  currently unused.
+        - *kw* - currently unused.
+
+      **Returns**:
+
+       - Tuple of strings, which represent paths relative to current working
+         directory (for given environment).
+    """
+    # os.path.relpath is available only on python >= 2.6. We use our own
+    # implementation. It's taken from BareNecessities package:
+    #   http://jimmyg.org/work/code/barenecessities/index.html
+    from posixpath import curdir
+    def relpath(path, start=curdir):
+        import posixpath
+        """Return a relative version of a path"""
+        if not path:
+           raise ValueError("no path specified")
+        start_list = posixpath.abspath(start).split(posixpath.sep)
+        path_list = posixpath.abspath(path).split(posixpath.sep)
+        # Work out how much of the filepath is shared by start and path.
+        i = len(posixpath.commonprefix([start_list, path_list]))
+        rel_list = [posixpath.pardir] * (len(start_list)-i) + path_list[i:]
+        if not rel_list:
+           return posixpath.curdir
+        return posixpath.join(*rel_list)
+    import os 
+    import SCons.Node.FS
+    rpaths = ()
+    cwd =  self.env.fs.getcwd().get_abspath()
+    for node in nodes:
+      rpath = None
+      if isinstance(node, SCons.Node.FS.Base):
+        rpath = relpath(node.get_abspath(), cwd)
+      # FIXME: Other types possible here?
+      if rpath is not None:
+        rpaths += (rpath,)
+    return rpaths
+#############################################################################
+   
+#############################################################################
+def _init_po_files(target, source, env):
+  """ Action function for `POInit` builder. """
+  nop = lambda target, source, env : 0
+  if env.has_key('POAUTOINIT'):
+    autoinit = env['POAUTOINIT']
+  else:
+    autoinit = False
+  # Well, if everything outside works well, this loop should do single
+  # iteration. Otherwise we are rebuilding all the targets even, if just
+  # one has changed (but is this out fault?).
+  for tgt in target:
+    if not tgt.exists():
+      if autoinit:
+        action = SCons.Action.Action('$MSGINITCOM', '$MSGINITCOMSTR')
+      else:
+        msg = 'File ' + repr(str(tgt)) + ' does not exist. ' \
+            + 'If you are a translator, you can create it through: \n' \
+            + '$MSGINITCOM'
+        action = SCons.Action.Action(nop, msg)
+      status = action([tgt], source, env)
+      if status: return status
+  return 0
+#############################################################################
+
+#############################################################################
+def _detect_xgettext(env):
+  """ Detects *xgettext(1)* binary """
+  if env.has_key('XGETTEXT'):
+    return env['XGETTEXT']
+  xgettext = env.Detect('xgettext');
+  if xgettext:
+    return xgettext
+  raise SCons.Errors.StopError(XgettextNotFound,"Could not detect xgettext")
+  return None
+#############################################################################
+def _xgettext_exists(env):
+  return _detect_xgettext(env)
+#############################################################################
+
+#############################################################################
+def _detect_msginit(env):
+  """ Detects *msginit(1)* program. """
+  if env.has_key('MSGINIT'):
+    return env['MSGINIT']
+  msginit = env.Detect('msginit');
+  if msginit:
+    return msginit
+  raise SCons.Errors.StopError(MsginitNotFound, "Could not detect msginit")
+  return None
+#############################################################################
+def _msginit_exists(env):
+  return  _detect_msginit(env)
+#############################################################################
+
+#############################################################################
+def _detect_msgmerge(env):
+  """ Detects *msgmerge(1)* program. """
+  if env.has_key('MSGMERGE'):
+    return env['MSGMERGE']
+  msgmerge = env.Detect('msgmerge');
+  if msgmerge:
+    return msgmerge
+  raise SCons.Errors.StopError(MsgmergeNotFound, "Could not detect msgmerge")
+  return None
+#############################################################################
+def _msgmerge_exists(env):
+  return  _detect_msgmerge(env)
+#############################################################################
+
+#############################################################################
+def _detect_msgfmt(env):
+  """ Detects *msgmfmt(1)* program. """
+  if env.has_key('MSGFMT'):
+    return env['MSGFMT']
+  msgfmt = env.Detect('msgfmt');
+  if msgfmt:
+    return msgfmt
+  raise SCons.Errors.StopError(MsgfmtNotFound, "Could not detect msgfmt")
+  return None
+#############################################################################
+def _msgfmt_exists(env):
+  return _detect_msgfmt(env)
+#############################################################################
+
+#############################################################################
+def tool_list(platform, env):
+  """ List tools that shall be generated by top-level `gettext` tool """
+  return [ 'xgettext', 'msginit', 'msgmerge', 'msgfmt' ]
+#############################################################################
+

src/engine/SCons/Tool/gettext.py

+"""gettext tool
+"""
+
+
+# __COPYRIGHT__
+# 
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+#############################################################################
+def generate(env,**kw):
+  import SCons.Tool
+  from SCons.Tool.GettextCommon \
+    import  _translate, tool_list
+  for t in tool_list(env['PLATFORM'], env):
+    env.Tool(t)
+  env.AddMethod(_translate, 'Translate')
+#############################################################################
+
+#############################################################################
+def exists(env):
+  from SCons.Tool.GettextCommon \
+  import _xgettext_exists, _msginit_exists, \
+         _msgmerge_exists, _msgfmt_exists
+  return _xgettext_exists(env) and _msginit_exists(env) \
+     and _msgmerge_exists(env) and _msgfmt_exists(env)
+#############################################################################

src/engine/SCons/Tool/gettext.xml

+<!--
+__COPYRIGHT__
+
+This file is processed by the bin/SConsDoc.py module.
+See its __doc__ string for a discussion of the format.
+-->
+<tool name="gettext">
+<summary>
+This is actually a toolset, which supports internationalization and
+localization of sofware being constructed with SCons. The toolset loads
+following tools:
+
+<itemizedlist mark='opencircle'>
+  <listitem><para>
+    &t-link-xgettext; - to extract internationalized messages from source code to 
+    <literal>POT</literal> file(s),
+  </para></listitem>
+  <listitem><para>
+    &t-link-msginit; - may be optionally used to initialize <literal>PO</literal>
+    files,
+  </para></listitem>
+  <listitem><para>
+    &t-link-msgmerge; - to update <literal>PO</literal> files, that already contain
+    translated messages,</para></listitem>
+  <listitem><para>
+    &t-link-msgfmt; - to compile textual <literal>PO</literal> file to binary
+    installable <literal>MO</literal> file.
+  </para></listitem>
+</itemizedlist>
+
+When you enable &t-gettext;, it internally loads all abovementioned tools,
+so you're encouraged to see their individual documentation.
+
+Each of the above tools provides its own builder(s) which may be used to
+perform particular activities related to software internationalization. You
+may be however interested in <emphasis>top-level</emphasis> builder
+&b-Translate; described few paragraphs later.
+
+To use &t-gettext; tools add <literal>'gettext'</literal> tool to your
+environment:
+<example>
+  env = Environment( tools = ['default', 'gettext'] )
+</example>
+</summary>
+<sets>
+</sets>
+<uses>
+<!-- PLATFORM -->
+</uses>
+</tool>
+
+<builder name="Translate">
+<summary>
+This pseudo-builder belongs to &t-link-gettext; toolset. The builder extracts
+internationalized messages from source files, updates <literal>POT</literal>
+template (if necessary) and then updates <literal>PO</literal> translations (if
+necessary). If &cv-link-POAUTOINIT; is set, missing <literal>PO</literal> files
+will be automatically created (i.e. without translator person intervention).
+The variables &cv-link-LINGUAS_FILE; and &cv-link-POTDOMAIN; are taken into
+acount too. All other construction variables used by &b-link-POTUpdate;, and
+&b-link-POUpdate; work here too.
+
+<emphasis>Example 1</emphasis>.
+The simplest way is to specify input files and output languages inline in
+a SCons script when invoking &b-Translate;
+<example>
+# SConscript in 'po/' directory
+env = Environment( tools = ["default", "gettext"] )
+env['POAUTOINIT'] = 1
+env.Translate(['en','pl'], ['../a.cpp','../b.cpp']) 
+</example>
+
+<emphasis>Example 2</emphasis>.
+If you wish, you may also stick to conventional style known from
+<productname>autotools</productname>, i.e. using
+<filename>POTFILES.in</filename> and <filename>LINGUAS</filename> files
+<example>
+# LINGUAS
+en pl 
+#end
+</example>
+
+<example>
+# POTFILES.in
+a.cpp
+b.cpp
+# end
+</example>
+
+<example>
+# SConscript
+env = Environment( tools = ["default", "gettext"] )
+env['POAUTOINIT'] = 1
+env['XGETTEXTPATH'] = ['../']
+env.Translate(LINGUAS_FILE = 1, XGETTEXTFROM = 'POTFILES.in') 
+</example>
+
+The last approach is perhaps the recommended one. It allows easily split
+internationalization/localization onto separate SCons scripts, where a script
+in source tree is responsible for translations (from sources to
+<literal>PO</literal> files) and script(s) under variant directories are
+responsible for compilation of <literal>PO</literal> to <literal>MO</literal>
+files to and for installation of <literal>MO</literal> files. The "gluing
+factor" synchronizing these two scripts is then the content of
+<filename>LINGUAS</filename> file.  Note, that the updated
+<literal>POT</literal> and <literal>PO</literal> files are usually going to be
+committed back to the repository, so they must be updated within the source
+directory (and not in variant directories). Additionaly, the file listing of
+<filename>po/</filename> directory contains <filename>LINGUAS</filename> file,
+so the source tree looks familiar to translators, and they may work with the
+project in their usual way.
+
+<emphasis>Example 3</emphasis>.
+Let's prepare a development tree as below
+<example>
+ project/
+  + SConstruct
+  + build/        
+  + src/
+      + po/
+          + SConscript
+          + SConscript.i18n
+          + POTFILES.in
+          + LINGUAS
+</example>
+with <filename>build</filename> being variant directory. Write the top-level
+<filename>SConstruct</filename> script as follows
+<example>
+  # SConstruct
+  env = Environment( tools = ["default", "gettext"] )
+  VariantDir('build', 'src', duplicate = 0)
+  env['POAUTOINIT'] = 1
+  SConscript('src/po/SConscript.i18n', exports = 'env')
+  SConscript('build/po/SConscript', exports = 'env')
+</example>
+the <filename>src/po/SConscript.i18n</filename> as
+<example>
+  # src/po/SConscript.i18n
+  Import('env')
+  env.Translate(LINGUAS_FILE=1, XGETTEXTFROM='POTFILES.in', XGETTEXTPATH=['../'])
+</example>
+and the <filename>src/po/SConscript</filename>
+<example>
+  # src/po/SConscript
+  Import('env')
+  env.MOFiles(LINGUAS_FILE = 1)
+</example>
+Such setup produces <literal>POT</literal> and <literal>PO</literal> files
+under source tree in <filename>src/po/</filename> and binary
+<literal>MO</literal> files under variant tree in
+<filename>build/po/</filename>. This way the <literal>POT</literal> and
+<literal>PO</literal> files are separated from other output files, which must
+not be committed back to source repositories (e.g. <literal>MO</literal>
+files).
+
+<note><para>In above example, the <literal>PO</literal> files are not updated,
+nor created automatically when you issue <command>scons '.'</command> command.
+The files must be updated (created) by hand via <command>scons
+po-update</command> and then <literal>MO</literal> files can be compiled by
+running <command>scons '.'</command>.</para></note>
+
+</summary>
+</builder>
+
+<cvar name="POTDOMAIN">
+<summary>
+The &cv-POTDOMAIN; defines default domain, used to generate
+<literal>POT</literal> filename as <filename>&cv-POTDOMAIN;.pot</filename> when
+no <literal>POT</literal> file name is provided by the user. This applies to
+&b-link-POTUpdate;, &b-link-POInit; and &b-link-POUpdate; builders (and
+builders, that use them, e.g. &b-Translate;). Normally (if &cv-POTDOMAIN; is
+not defined), the builders use <filename>messages.pot</filename> as default
+<literal>POT</literal> file name.
+</summary>
+</cvar>
+
+<cvar name="POAUTOINIT">
+<summary>
+The &cv-POAUTOINIT; variable, if set to <literal>True</literal> (on non-zero
+numeric value), let the &t-link-msginit; tool to automatically initialize
+<emphasis>missing</emphasis> <literal>PO</literal> files with
+<command>msginit(1)</command>.  This applies to both,
+&b-link-POInit; and &b-link-POUpdate; builders (and others that use any of
+them).
+</summary>
+</cvar>
+
+<cvar name="LINGUAS_FILE">
+<summary>
+The &cv-LINGUAS_FILE; defines file(s) containing list of additional linguas
+to be processed by &b-link-POInit;, &b-link-POUpdate; or &b-link-MOFiles;
+builders. It also affects &b-link-Translate; builder. If the variable contains
+a string, it defines name of the list file. The &cv-LINGUAS_FILE; may be a
+list of file names as well. If &cv-LINGUAS_FILE; is set to
+<literal>True</literal> (or non-zero numeric value), the list will be read from
+default file named
+<filename>LINGUAS</filename>.
+
+</summary>
+</cvar>

src/engine/SCons/Tool/msgfmt.py

+""" msgfmt tool """
+
+# __COPYRIGHT__
+# 
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+from SCons.Builder import BuilderBase
+#############################################################################
+class _MOFileBuilder(BuilderBase):
+  """ The builder class for `MO` files.
+  
+  The reason for this builder to exists and its purpose is quite simillar 
+  as for `_POFileBuilder`. This time, we extend list of sources, not targets,
+  and call `BuilderBase._execute()` only once (as we assume single-target
+  here).
+  """
+
+  def _execute(self, env, target, source, *args, **kw):
+    # Here we add support for 'LINGUAS_FILE' keyword. Emitter is not suitable
+    # in this case, as it is called too late (after multiple sources
+    # are handled single_source builder.
+    import SCons.Util
+    from SCons.Tool.GettextCommon import _read_linguas_from_files
+    linguas_files = None
+    if env.has_key('LINGUAS_FILE') and env['LINGUAS_FILE'] is not None:
+      linguas_files = env['LINGUAS_FILE']
+      # This should prevent from endless recursion. 
+      env['LINGUAS_FILE'] = None
+      # We read only languages. Suffixes shall be added automatically.
+      linguas = _read_linguas_from_files(env, linguas_files)
+      if SCons.Util.is_List(source):
+        source.extend(linguas)
+      elif source is not None:
+        source = [source] + linguas
+      else:
+        source = linguas
+    result = BuilderBase._execute(self,env,target,source,*args, **kw)
+    if linguas_files is not None:
+      env['LINGUAS_FILE'] = linguas_files
+    return result
+#############################################################################
+
+#############################################################################
+def _create_mo_file_builder(env, **kw):
+  """ Create builder object for `MOFiles` builder """
+  import SCons.Action
+  # FIXME: What factory use for source? Ours or their?
+  kw['action'] = SCons.Action.Action('$MSGFMTCOM','$MSGFMTCOMSTR')
+  kw['suffix'] = '$MOSUFFIX'
+  kw['src_suffix'] = '$POSUFFIX'
+  kw['src_builder'] = '_POUpdateBuilder'
+  kw['single_source'] = True 
+  return _MOFileBuilder(**kw)
+#############################################################################
+
+#############################################################################
+def generate(env,**kw):
+  """ Generate `msgfmt` tool """
+  import SCons.Util
+  from SCons.Tool.GettextCommon import _detect_msgfmt
+  env['MSGFMT'] = _detect_msgfmt(env)
+  env.SetDefault(
+    MSGFMTFLAGS = [ SCons.Util.CLVar('-c') ],
+    MSGFMTCOM = '$MSGFMT $MSGFMTFLAGS -o $TARGET $SOURCE',
+    MSGFMTCOMSTR = '',
+    MOSUFFIX = ['.mo'],
+    POSUFFIX = ['.po']
+  )
+  env.Append( BUILDERS = { 'MOFiles'  : _create_mo_file_builder(env) } )
+#############################################################################
+
+#############################################################################
+def exists(env):
+  """ Check if the tool exists """
+  from SCons.Tool.GettextCommon import _msgfmt_exists
+  return _msgfmt_exists(env)
+#############################################################################
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:

src/engine/SCons/Tool/msgfmt.xml

+<!--
+__COPYRIGHT__
+
+This file is processed by the bin/SConsDoc.py module.
+See its __doc__ string for a discussion of the format.
+-->
+<tool name="msgfmt">
+<summary>
+This scons tool is a part of scons &t-link-gettext; toolset. It provides scons
+interface to <command>msgfmt(1)</command> command, which generates binary
+message catalog (<literal>MO</literal>) from a textual translation description
+(<literal>PO</literal>).
+</summary>
+<sets>
+MOSUFFIX
+MSGFMT
+MSGFMTCOM
+MSGFMTCOMSTR
+MSGFMTFLAGS
+POSUFFIX
+</sets>
+<uses>
+LINGUAS_FILE
+</uses>
+</tool>
+
+<builder name="MOFiles">
+<summary>
+This builder belongs to &t-link-msgfmt; tool. The builder compiles
+<literal>PO</literal> files to <literal>MO</literal> files.
+
+<emphasis>Example 1</emphasis>.
+Create <filename>pl.mo</filename> and <filename>en.mo</filename> by compiling
+<filename>pl.po</filename> and <filename>en.po</filename>:
+<example>
+  # ...
+  env.MOFiles(['pl', 'en'])
+</example>
+
+<emphasis>Example 2</emphasis>.
+Compile files for languages defined in <filename>LINGUAS</filename> file:
+<example>
+  # ...
+  env.MOFiles(LINGUAS_FILE = 1)
+</example>
+
+<emphasis>Example 3</emphasis>.
+Create <filename>pl.mo</filename> and <filename>en.mo</filename> by compiling
+<filename>pl.po</filename> and <filename>en.po</filename> plus files for
+languages defined in <filename>LINGUAS</filename> file:
+<example>
+  # ...
+  env.MOFiles(['pl', 'en'], LINGUAS_FILE = 1)
+</example>
+
+<emphasis>Example 4</emphasis>.
+Compile files for languages defined in <filename>LINGUAS</filename> file 
+(another version):
+<example>
+  # ...
+  env['LINGUAS_FILE'] = 1
+  env.MOFiles()
+</example>
+</summary>
+</builder>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="MOSUFFIX">
+<summary>
+Suffix used for <literal>MO</literal> files (default: <literal>'.mo'</literal>).
+See &t-link-msgfmt; tool and &b-link-MOFiles; builder.
+</summary>
+</cvar>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="MSGFMT">
+<summary>
+Absolute path to <command>msgfmt(1)</command> binary, found by
+<function>Detect()</function>.
+See &t-link-msgfmt; tool and &b-link-MOFiles; builder.
+</summary>
+</cvar>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="MSGFMTCOM">
+<summary>
+Complete command line to run <command>msgfmt(1)</command> program.
+See &t-link-msgfmt; tool and &b-link-MOFiles; builder.
+</summary>
+</cvar>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="MSGFMTCOMSTR">
+<summary>
+String to display when <command>msgfmt(1)</command> is invoked 
+(default: <literal>''</literal>, which means ``print &cv-link-MSGFMTCOM;'').
+See &t-link-msgfmt; tool and &b-link-MOFiles; builder.
+</summary>
+</cvar>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="MSGFMTFLAGS">
+<summary>
+Additional flags to <command>msgfmt(1)</command>.
+See &t-link-msgfmt; tool and &b-link-MOFiles; builder.
+</summary>
+</cvar>

src/engine/SCons/Tool/msginit.py

+""" msginit tool 
+
+Tool specific initialization of msginit tool.
+"""
+
+# __COPYRIGHT__
+# 
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.Warnings
+import SCons.Builder
+import re
+
+#############################################################################
+def _optional_no_translator_flag(env):
+  """ Return '--no-translator' flag if we run *msginit(1)*  in non-interactive
+      mode."""
+  import SCons.Util
+  if env.has_key('POAUTOINIT'):
+    autoinit = env['POAUTOINIT']
+  else:
+    autoinit = False
+  if autoinit:
+    return [SCons.Util.CLVar('--no-translator')]
+  else:
+    return [SCons.Util.CLVar('')]
+#############################################################################
+
+#############################################################################
+def _POInitBuilder(env, **kw):
+  """ Create builder object for `POInit` builder. """
+  import SCons.Action
+  from SCons.Tool.GettextCommon import _init_po_files, _POFileBuilder
+  action = SCons.Action.Action(_init_po_files, None)
+  return _POFileBuilder(env, action=action, target_alias='$POCREATE_ALIAS')
+#############################################################################
+  
+#############################################################################
+from SCons.Environment import _null
+#############################################################################
+def _POInitBuilderWrapper(env, target=None, source=_null, **kw):
+  """ Wrapper for _POFileBuilder. We use it to make user's life easier.
+  
+  This wrapper checks for `$POTDOMAIN` construction variable (or override in
+  `**kw`) and treats it appropriatelly. 
+  """
+  if source is _null:
+    if 'POTDOMAIN' in kw:
+      domain = kw['POTDOMAIN']
+    elif env.has_key('POTDOMAIN'):
+      domain = env['POTDOMAIN']
+    else:
+      domain = 'messages'
+    source = [ domain ] # NOTE: Suffix shall be appended automatically
+  return env._POInitBuilder(target, source, **kw)
+#############################################################################
+
+#############################################################################
+def generate(env,**kw):
+  """ Generate the `msginit` tool """
+  import SCons.Util
+  from SCons.Tool.GettextCommon import _detect_msginit
+  env['MSGINIT'] = _detect_msginit(env)
+  msginitcom = '$MSGINIT ${_MSGNoTranslator(__env__)} -l ${_MSGINITLOCALE}' \
+             + ' $MSGINITFLAGS -i $SOURCE -o $TARGET'
+  # NOTE: We set POTSUFFIX here, in case the 'xgettext' is not loaded
+  #       (sometimes we really don't need it)
+  env.SetDefault(
+    POSUFFIX = ['.po'],
+    POTSUFFIX = ['.pot'],
+    _MSGINITLOCALE = '${TARGET.filebase}',
+    _MSGNoTranslator = _optional_no_translator_flag,
+    MSGINITCOM = msginitcom,
+    MSGINITCOMSTR = '',
+    MSGINITFLAGS = [ ],
+    POAUTOINIT = False,
+    POCREATE_ALIAS = 'po-create'
+  )
+  env.Append( BUILDERS = { '_POInitBuilder' : _POInitBuilder(env) } )
+  env.AddMethod(_POInitBuilderWrapper, 'POInit')
+  env.AlwaysBuild(env.Alias('$POCREATE_ALIAS'))
+#############################################################################
+
+#############################################################################
+def exists(env):
+  """ Check if the tool exists """
+  from SCons.Tool.GettextCommon import _msginit_exists
+  return  _msginit_exists(env)
+#############################################################################
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:

src/engine/SCons/Tool/msginit.xml

+<!--
+__COPYRIGHT__
+
+This file is processed by the bin/SConsDoc.py module.
+See its __doc__ string for a discussion of the format.
+-->
+<tool name="msginit">
+<summary>
+This scons tool is a part of scons &t-link-gettext; toolset. It provides
+scons interface to <command>msginit(1)</command> program, which creates new
+<literal>PO</literal> file, initializing the meta information with values from
+user's environment (or options).
+</summary>
+<sets>
+MSGINIT
+MSGINITCOM
+MSGINITCOMSTR
+MSGINITFLAGS
+POAUTOINIT
+POCREATE_ALIAS
+POSUFFIX
+POTSUFFIX
+_MSGINITLOCALE
+</sets>
+<uses>
+POTDOMAIN
+LINGUAS_FILE
+POAUTOINIT
+</uses>
+</tool>
+
+<builder name="POInit">
+<summary>
+This builder belongs to &t-link-msginit; tool. The builder initializes missing
+<literal>PO</literal> file(s) if &cv-link-POAUTOINIT; is set.  If
+&cv-link-POAUTOINIT; is not set (default), &b-POInit; prints instruction for
+user (that is supposed to be a translator), telling how the
+<literal>PO</literal> file should be initialized. In normal projects
+<emphasis>you should not use &b-POInit; and use &b-link-POUpdate;
+instead</emphasis>. &b-link-POUpdate; chooses intelligently between
+<command>msgmerge(1)</command> and <command>msginit(1)</command>. &b-POInit;
+always uses <command>msginit(1)</command> and should be regarded as builder for
+special purposes or for temporary use (e.g. for quick, one time initialization
+of a bunch of <literal>PO</literal> files) or for tests.
+
+Target nodes defined through &b-POInit; are not built by default (they're
+<literal>Ignore</literal>d from <literal>'.'</literal> node) but are added to
+special <literal>Alias</literal> (<literal>'po-create'</literal> by default).
+The alias name may be changed through the &cv-link-POCREATE_ALIAS;
+construction variable. All <literal>PO</literal> files defined through
+&b-POInit; may be easily initialized by <command>scons po-create</command>.
+
+<emphasis>Example 1</emphasis>.
+Initialize <filename>en.po</filename> and <filename>pl.po</filename> from
+<filename>messages.pot</filename>:
+<example>
+  # ...
+  env.POInit(['en', 'pl']) # messages.pot --&gt; [en.po, pl.po] 
+</example>
+
+<emphasis>Example 2</emphasis>.
+Initialize <filename>en.po</filename> and <filename>pl.po</filename> from
+<filename>foo.pot</filename>:
+<example>
+  # ...
+  env.POInit(['en', 'pl'], ['foo']) # foo.pot --&gt; [en.po, pl.po] 
+</example>
+
+<emphasis>Example 3</emphasis>.
+Initialize <filename>en.po</filename> and <filename>pl.po</filename> from
+<filename>foo.pot</filename> but using &cv-link-POTDOMAIN; construction
+variable:
+<example>
+  # ...
+  env.POInit(['en', 'pl'], POTDOMAIN='foo') # foo.pot --&gt; [en.po, pl.po] 
+</example>
+
+<emphasis>Example 4</emphasis>.
+Initialize <literal>PO</literal> files for languages defined in
+<filename>LINGUAS</filename> file. The files will be initialized from template
+<filename>messages.pot</filename>:
+<example>
+  # ...
+  env.POInit(LINGUAS_FILE = 1) # needs 'LINGUAS' file
+</example>
+
+<emphasis>Example 5</emphasis>.
+Initialize <filename>en.po</filename> and <filename>pl.pl</filename>
+<literal>PO</literal> files plus files for languages defined in
+<filename>LINGUAS</filename> file. The files will be initialized from template
+<filename>messages.pot</filename>:
+<example>
+  # ...
+  env.POInit(['en', 'pl'], LINGUAS_FILE = 1)
+</example>
+
+<emphasis>Example 6</emphasis>.
+You may preconfigure your environment first, and then initialize
+<literal>PO</literal> files:
+<example>
+  # ...
+  env['POAUTOINIT'] = 1
+  env['LINGUAS_FILE'] = 1
+  env['POTDOMAIN'] = 'foo'
+  env.POInit()
+</example>
+which has same efect as:
+<example>
+  # ...
+  env.POInit(POAUTOINIT = 1, LINGUAS_FILE = 1, POTDOMAIN = 'foo')
+</example>
+</summary>
+</builder>
+
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="POCREATE_ALIAS">
+<summary>
+Common alias for all <literal>PO</literal> files created with &b-POInit;
+builder (default: <literal>'po-create'</literal>).
+See &t-link-msginit; tool and &b-link-POInit; builder.
+</summary>
+</cvar>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="POSUFFIX">
+<summary>
+Suffix used for <literal>PO</literal> files (default: <literal>'.po'</literal>)
+See &t-link-msginit; tool and &b-link-POInit; builder.
+</summary>
+</cvar>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="MSGINIT">
+<summary>
+Path to <command>msginit(1)</command> program (found via
+<literal>Detect()</literal>).
+See &t-link-msginit; tool and &b-link-POInit; builder.
+</summary>
+</cvar>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="MSGINITCOM">
+<summary>
+Complete command line to run <command>msginit(1)</command> program.
+See &t-link-msginit; tool and &b-link-POInit; builder.
+</summary>
+</cvar>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="MSGINITCOMSTR">
+<summary>
+String to display when <command>msginit(1)</command> is invoked 
+(default: <literal>''</literal>, which means ``print &cv-link-MSGINITCOM;'').
+See &t-link-msginit; tool and &b-link-POInit; builder.
+</summary>
+</cvar>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="MSGINITFLAGS">
+<summary>
+List of additional flags to <command>msginit(1)</command> (default:
+<literal>[]</literal>).
+See &t-link-msginit; tool and &b-link-POInit; builder.
+</summary>
+</cvar>
+<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
+<cvar name="_MSGINITLOCALE">
+<summary>
+Internal ``macro''. Computes locale (language) name based on target filename
+(default: <literal>'${TARGET.filebase}' </literal>).
+</summary>
+See &t-link-msginit; tool and &b-link-POInit; builder.
+</cvar>

src/engine/SCons/Tool/msgmerge.py

+""" msgmerget tool 
+
+Tool specific initialization for `msgmerge` tool.
+"""
+
+# __COPYRIGHT__
+# 
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+#############################################################################
+def _update_or_init_po_files(target, source, env):
+  """ Action function for `POUpdate` builder """
+  import SCons.Action
+  from SCons.Tool.GettextCommon import _init_po_files
+  for tgt in target:
+    if tgt.rexists():
+      action = SCons.Action.Action('$MSGMERGECOM', '$MSGMERGECOMSTR')
+    else:
+      action = _init_po_files
+    status = action([tgt], source, env)
+    if status : return status
+  return 0
+#############################################################################
+
+#############################################################################
+def _POUpdateBuilder(env, **kw):
+  """ Create an object of `POUpdate` builder """
+  import SCons.Action
+  from SCons.Tool.GettextCommon import _POFileBuilder
+  action = SCons.Action.Action(_update_or_init_po_files, None)
+  return _POFileBuilder(env, action=action, target_alias='$POUPDATE_ALIAS')
+#############################################################################
+
+#############################################################################
+from SCons.Environment import _null
+#############################################################################
+def _POUpdateBuilderWrapper(env, target=None, source=_null, **kw):
+  """ Wrapper for `POUpdate` builder - make user's life easier """
+  if source is _null:
+    if 'POTDOMAIN' in kw:
+      domain = kw['POTDOMAIN']
+    elif env.has_key('POTDOMAIN') and env['POTDOMAIN']:
+      domain = env['POTDOMAIN']
+    else:
+      domain = 'messages'
+    source = [ domain ] # NOTE: Suffix shall be appended automatically
+  return env._POUpdateBuilder(target, source, **kw)
+#############################################################################
+
+#############################################################################
+def generate(env,**kw):
+  """ Generate the `xgettext` tool """
+  from SCons.Tool.GettextCommon import _detect_msgmerge
+  env['MSGMERGE'] = _detect_msgmerge(env)
+  env.SetDefault(
+    POTSUFFIX = ['.pot'],
+    POSUFFIX = ['.po'],
+    MSGMERGECOM = '$MSGMERGE  $MSGMERGEFLAGS --update $TARGET $SOURCE',
+    MSGMERGECOMSTR = '',
+    MSGMERGEFLAGS = [ ],
+    POUPDATE_ALIAS = 'po-update'
+  )
+  env.Append(BUILDERS = { '_POUpdateBuilder':_POUpdateBuilder(env) })
+  env.AddMethod(_POUpdateBuilderWrapper, 'POUpdate')
+  env.AlwaysBuild(env.Alias('$POUPDATE_ALIAS'))
+#############################################################################
+
+#############################################################################
+def exists(env):
+  """ Check if the tool exists """
+  from SCons.Tool.GettextCommon import _msgmerge_exists
+  return  _msgmerge_exists(env)
+#############################################################################
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:

src/engine/SCons/Tool/msgmerge.xml

+<!--
+__COPYRIGHT__
+
+This file is processed by the bin/SConsDoc.py module.
+See its __doc__ string for a discussion of the format.
+-->
+<tool name="msgmerge">
+<summary>
+This scons tool is a part of scons &t-link-gettext; toolset. It provides
+scons interface to <command>msgmerge(1)</command> command, which merges two
+Uniform style <filename>.po</filename> files together. 
+</summary>
+<sets>
+MSGMERGE
+MSGMERGECOM 
+MSGMERGECOMSTR