Updated copyright text/header in most source files.
[geda-gaf/peter-b.git] / build-tools / desktop-i18n
blobe3c44d401a6888eb2314bf7fa007de7072546342
1 #!/bin/sh
2 # -*-Shell-script-*-
3 # Helper script for translating desktop integration data
4 # Copyright (C) 2009 Peter Brett <peter@peter-b.co.uk>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 # usage [EXITSTATUS]
21 # ------------------
22 # Print a simple help message, then exit with EXITSTATUS
23 usage() {
24 cat <<EOF
25 Carry out translation tasks on desktop integration data.
27 Usage:
28 desktop-i18n --extract <options> -- <xgettext_options>
29 desktop-i18n --create <options> INFILE OUTFILE
30 desktop-i18n --setup
32 Operating modes:
33 --extract Extract strings by calling xgettext
34 --create Substitute translated strings
35 --setup Setup a source tree to use desktop-i18n
36 --help Print this message
38 Options for --extract mode:
39 --xgettext=XGETTEXT
40 Specify xgettext executable to use
42 Options for --create mode:
43 --gettext=GETTEXT
44 Specify gettext executable to use
45 --domain=TEXTDOMAIN
46 Retrieve translated bmessages from TEXTDOMAIN
47 --localedir=TEXTDOMAINDIR
48 Retrieve message catalog from TEXTDOMAINDIR
49 --lang=LANG Add a language to translate messages into
51 In order for this to work, all strings to be matched must be on a
52 single line. In a .desktop file, a translatable name-value pair must
53 have the desired name prefixed by an underscore. For example:
55 _Comment=gEDA Schematic Editor
57 In a MIME info file, XML tag pairs where the tagname begins with an
58 underscore are recognized. Both tags must be on the same line, and
59 the tag must be the only XML content on the line. Whitespace at the
60 start of the line before the opening tag is preserved. For example:
62 <_comment>gEDA circuit schematic</_comment>
64 Do not include double-quotes (") or slashes (\) in translatable
65 strings.
66 EOF
67 exit $1
70 # extract_desktop INFILE
71 # ----------------------
72 # Parse desktop file data from standard input and generate C on
73 # standard output. If an error occurs, a message is printed blaming
74 # INFILE.
75 extract_desktop() {
76 # First argument is name of file being processed
77 echo "/* Generated from $1 by desktop-i18n */"
78 echo
79 # Loop over each line of standard input
80 n=0
81 while read REPLY; do
82 n=`expr $n + 1`
83 regexp='^_\([^=]*\)=\(.*\)$'
84 if ! (echo $REPLY | grep $regexp > /dev/null); then
85 continue
87 name=`echo $REPLY | sed -e "s:$regexp:\1:"`
88 msgid=`echo $REPLY | sed -e "s:$regexp:\2:"`
90 # Test for bad characters
91 if (echo $msgid | grep '["\\]' > /dev/null); then
92 echo "$1:$n: msgid contains invalid character" >&2
93 exit 2
96 # Generate output line
97 if test "x$name" != x -a "x$msgid" != x; then
98 echo "_(\"$msgid\");"
99 else
100 echo "$1:$n: name or msgid is empty" >&2
101 exit 2
103 done
107 # extract_xml INFILE
108 # ------------------
109 # Parse XML mimeinfo data from standard input and generate C on
110 # standard output. If an error occurs, a message is printed blaming
111 # INFILE.
112 extract_xml() {
113 echo "/* Generated from $1 by desktop-i18n */"
114 echo
115 # Loop over each line of standard input
117 while read REPLY; do
118 n=`expr $n + 1`
119 regexp='<_\([a-zA-Z][a-zA-Z]*\)>\(.*\)</_\1>'
120 if ! (echo $REPLY | grep $regexp > /dev/null); then
121 continue
123 name=`echo $REPLY | sed -e "s:$regexp:\1:"`
124 msgid=`echo $REPLY | sed -e "s:$regexp:\2:"`
126 # Test for bad characters
127 if (echo $msgid | grep '["\\]' > /dev/null); then
128 echo "$1:$n: msgid contains invalid character" >&2
129 exit 2
132 # Generate output line
133 if test "x$name" != x -a "x$msgid" != x; then
134 echo "_(\"$msgid\");"
135 else
136 echo "$1:$n: name or msgid is empty" >&2
137 exit 2
139 done
142 # do_extract [OPTION]... -- [XGETTEXT_OPTION]...
143 # ----------------------------------------------
144 # A wrapper around xgettext. It identifies the file lists and search
145 # directories being used by xgettext, and from them any desktop or
146 # mimeinfo files to be processed.
148 # It then creates a new private directory, and adds it to the xgettext
149 # search path. It creates a new file list and set of preprocessed
150 # files in that directory, and then calls xgettext (preserving all
151 # other original arguments).
152 do_extract() {
153 XGETTEXT=xgettext
155 # First we have to process the command-line arguments
156 for arg; do
157 # Split into name=value
158 name=`echo $arg | sed -e's:=.*::'`
159 value=`echo $arg | sed -e's:^[^=]*=*::'`
161 if test "X$in_xg_args" = X; then
162 # This is an argument only for this script
163 case $name in
164 --xgettext) XGETTEXT="$value";;
165 --help) usage;;
166 --) in_xg_args=1;;
167 *) usage 1;;
168 esac
170 else
171 # This is an argument to xgettext. Luckily the Makefile only
172 # uses full-length arguments, and we only really care about the
173 # ones from there!
174 case $name in
175 --directory) search_dirs="$search_dirs $value";;
176 --files-from) file_lists="$file_lists $value";;
178 # We just want to pass this arg straight to xgettext, so
179 # stick it back on the end of the positional parameters
180 set x "$@" "$arg"; shift;;
181 esac
184 # Discard processed arg from positional parameters
185 shift
186 done
188 # If our private data directory exists, die. Otherwise, create it.
189 priv_dir=.po-input
190 if test -d $priv_dir; then
191 echo "desktop-i18n: $PWD/$priv_dir already exists"
192 exit 3
194 mkdir $priv_dir
196 # Process file lists if necessary
197 if test "X$file_lists" != X; then
198 # Extract names of files we need to preprocess
199 desktop_in=`cat $file_lists | grep ".desktop.in$"`
200 xml_in=`cat $file_lists | grep ".xml.in$"`
202 # Create a new POTFILES file which uses the postprocessed
203 # filenames instead of the original ones.
204 cat $file_lists | \
205 sed -e "s:.desktop.in$:.desktop.in.h:" -e "s:.xml.in$:.xml.in.h:" \
206 > $priv_dir/POTFILES
209 # Preprocess .desktop files
210 for f in $desktop_in; do
211 src=`_search_file $f $search_dirs` || { rm -rf $priv_dir; exit 3; }
212 mkdir -p $priv_dir/`dirname $f`
213 extract_desktop $f < $src > $priv_dir/$f.h
214 done
216 # Preprocess .xml files
217 for f in $xml_in; do
218 src=`_search_file $f $search_dirs` || { rm -rf $priv_dir; exit 3; }
219 mkdir -p $priv_dir/`dirname $f`
220 extract_xml $f < $src > $priv_dir/$f.h
221 done
223 # Call xgettext (recall we saved some args in $@)
224 gen_args="--files-from=$priv_dir/POTFILES --directory=$priv_dir"
225 for d in $search_dirs; do
226 gen_args="$gen_args --directory=$d"
227 done
228 $XGETTEXT $gen_args "$@"
230 # Clean up private directory
231 rm -rf $priv_dir
234 _search_file() {
235 f=$1
236 shift
237 for d in $@; do
238 if test -f "$d/$f"; then echo "$d/$f"; exit; fi
239 done
240 echo "desktop-i18n: Cannot find $f in xgettext search directories"
243 # create_desktop INFILE
244 # ---------------------
245 # Parse desktop file data from standard input and generate a
246 # translated desktop file on standard output. If an error occurs, a
247 # message is printed blaming INFILE.
248 create_desktop() {
249 # Loop over each line of standard input
251 while read REPLY; do
252 n=`expr $n + 1`
253 regexp='^_\([^=]*\)=\(.*\)$'
254 if ! (echo $REPLY | grep $regexp > /dev/null); then
255 echo $REPLY
256 continue
258 name=`echo $REPLY | sed -e "s:$regexp:\1:"`
259 msgid=`echo $REPLY | sed -e "s:$regexp:\2:"`
261 # Test for bad characters
262 if (echo $msgid | grep '["\\]' > /dev/null); then
263 echo "$1:$n: msgid contains invalid character" >&2
264 exit 2
267 # Generate first output line
268 echo "$name=$msgid"
270 # Generate language-specific output lines
271 for lang in $LINGUAS; do
272 msg=`LANGUAGE=$lang $GETTEXT "$msgid"`
273 # If translated message is unmodified, don't write an output
274 # line
275 if test "x$msg" = x -o "$msg" = "$msgid"; then
276 continue;
279 echo "$name[$lang]=$msg"
280 done
281 done
284 # create_xml INFILE
285 # -----------------
286 # Parse XML mimeinfo data from standard input and generate a
287 # translated mimeinfo file on standard output. If an error occurs, a
288 # message is printed blaming INFILE.
289 create_xml() {
290 # Loop over each line of standard input
292 while : ; do
294 # We have to do an ugly hack to avoid stripping whitespace.
295 saveIFS="$IFS"
296 IFS=
297 read REPLY || { IFS="$saveIFS" ; break; }
298 IFS="$saveIFS"
300 n=`expr $n + 1`
301 regexp='<_\([a-zA-Z][a-zA-Z]*\)>\(.*\)</_\1>'
302 if ! (echo $REPLY | grep $regexp > /dev/null); then
303 echo "$REPLY"
304 continue
306 name=`echo $REPLY | sed -e "s:$regexp:\1:"`
307 msgid=`echo $REPLY | sed -e "s:$regexp:\2:"`
308 prefix=`echo $REPLY | sed -e "s:^\(.*\)<_$name>.*:\1:"`
309 suffix=`echo $REPLY | sed -e "s:.*</_$name>\(.*\):\1:"`
311 # Test for bad characters
312 if (echo $msgid | grep '["\\]' > /dev/null); then
313 echo "$1:$n: msgid contains invalid character" >&2
314 exit 2
317 # Test for non-empty prefix/suffix
318 if test "x$prefix" != x -o "x$suffix" != x; then
319 echo "$1:$n: translatable tag must be alone on line" >&2
320 exit 2
323 # Generate first output line
324 echo "$REPLY" | sed -e "s:<_\($name\)>\(.*\)</_\1>:<\1>\2</\1>:"
326 # Generate language-specific output lines
327 for lang in $LINGUAS; do
328 msg=`LANGUAGE=$lang $GETTEXT "$msgid"`
329 # If translated message is unmodified, don't write an output
330 # line
331 if test "x$msg" = x -o "$msg" = "$msgid"; then
332 continue;
334 echo "$REPLY" | sed -e "s,<_\($name\)>\(.*\)</_\1>,<\1 xml:lang=\"$lang\">$msg</\1>,"
335 done
336 done
339 # do_create [OPTION]... INFILE OUTFILE
340 # ------------------------------------
341 # Substitutes translations into .desktop or mimeinfo files.
342 do_create() {
343 GETTEXT=gettext
345 # First process command-line arguments
346 for arg; do
347 # Split into name=value
348 name=`echo $arg | sed -e's:=.*::'`
349 value=`echo $arg | sed -e's:^[^=]*=*::'`
351 case $name in
352 --gettext) GETTEXT=$value;;
353 --domain) TEXTDOMAIN=$value;;
354 --localedir) TEXTDOMAINDIR=$value;;
355 --lang) LINGUAS="$LINGUAS $value";;
357 # Arg might be a filename, so save it at the end of the
358 # positional parameters
359 set x "$@" "$arg"; shift
360 esac
362 # Discard processed arg from positional parameters
363 shift
364 done
366 if test $# != 2; then usage 1; fi # Should only have 2 args left
367 INFILE=$1; OUTFILE=$2
368 if ! test -r $INFILE; then
369 echo "desktop-i18n: Cannot open $INFILE for reading."
370 exit 3
373 export TEXTDOMAIN
374 export GETTEXT
375 export TEXTDOMAINDIR
376 export LINGUAS
378 if (echo "$INFILE" | grep ".desktop.in$" > /dev/null); then
379 create_desktop $INFILE < $INFILE > $OUTFILE
380 exit 0
383 if (echo "$INFILE" | grep ".xml.in$" > /dev/null); then
384 create_xml $INFILE < $INFILE > $OUTFILE
385 exit 0
388 echo "desktop-i18n: $INFILE: Unrecognized extension"
389 exit 1
392 # do_setup [DIR]
393 # --------------
394 # Try to set up a source tree to use desktop-i18n.
396 # This is a nasty bit of hackery. We need to insert some rules into
397 # the Makefile.in.in installed by gettextize/autopoint so that make
398 # knows how to generate input for xgettext.
400 # Unfortunately, there's no nice way to do this, so we do it by
401 # appending some rules onto each Makefile.in.in, using the following
402 # procedure:
404 # 1. Look for configure.ac in DIR, or in cwd if DIR wasn't
405 # specified. If we can't find it, whinge.
406 # 2. If configure.ac doesn't have AX_DESKTOP_I18N, quit successfully.
407 # 3. Find anywhere where AC_CONFIG_FILES is called. For each
408 # Makefile.in found in the list of files to create:
409 # (a) Check for Makefile.in.in. If it doesn't exist, skip with a warning.
410 # (b) If Makefile.in.in contains the string DESKTOP_I18N_RULES,
411 # skip silently.
412 # (c) Append a chunk of rules onto Makefile.in.in
414 # Note that we can't use a po/Rules-* file because substitution is not
415 # carried out on these files.
416 do_setup() {
417 # Was DIR specified?
418 if test "x$1" = x; then srcdir=.; else srcdir=$1; fi
420 # Can we find configure.ac or configure.in?
421 for f in configure.ac configure.in; do
422 if test -r $srcdir/$f; then
423 ac_file=$srcdir/$f
424 break
426 done
427 if test "x$ac_file" = x; then
428 echo "Cannot find configure.ac or configure.in!"
429 exit 4
432 # Check that configure.ac is readable
433 if ! test -r $ac_file; then
434 echo "Cannot open $ac_file for reading."
435 exit 3
438 # Is the AX_DESKTOP_I18N macro present?
439 if ! grep AX_DESKTOP_I18N $ac_file > /dev/null; then
440 exit
443 # Now we use a piece of m4 code to try and discover all of the
444 # configuration files. This is UGLY AND BAD, because it only detects
445 # when AC_CONFIG_FILES is called in the main configure script (if
446 # AC_CONFIG_FILES is called by another macro somewhere, it won't be
447 # detected).
448 cat - $ac_file > conftest <<EOF
449 changequote([,])dnl
450 divert([-1])
451 define([AC_CONFIG_FILES], [divert([0])[\$1]divert([-1])])
453 conf_files=`m4 conftest`
454 rm conftest
456 # Look for any files called Makefile.in.
457 for f in $conf_files; do
458 # Discard any composition rules and prepend srcdir.
459 f=`echo "$f" | sed -e 's,:.*,,'`
460 f="$srcdir/$f"
462 # Skip files not called Makefile.in
463 if test `echo "$f" | sed -e 's:.*/::'` != Makefile.in; then
464 continue
467 # Check that a corresponding Makefile.in.in exists and we can
468 # read/write it
469 if ! test -r $f.in -a -w $f.in; then
470 echo "desktop-i18n: Cannot process $f.in"
471 continue
474 # Check that we haven't already hacked it
475 if grep DESKTOP_I18N_RULES $f.in > /dev/null; then
476 continue
479 # Append our rules
480 echo "desktop-i18n: modifying $f.in"
481 cat >> $f.in <<EOF
483 # DESKTOP_I18N_RULES (Do not edit or remove this line)
484 #####################################################################
485 # Makefile rules needed by the desktop-i18n tool.
486 # Copyright (C) 2009 Peter Brett <peter@peter-b.co.uk>
488 # This program is free software; you can redistribute it and/or modify
489 # it under the terms of the GNU General Public License as published by
490 # the Free Software Foundation; either version 2 of the License, or
491 # (at your option) any later version.
493 # This program is distributed in the hope that it will be useful,
494 # but WITHOUT ANY WARRANTY; without even the implied warranty of
495 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
496 # GNU General Public License for more details.
498 # You should have received a copy of the GNU General Public License
499 # along with this program; if not, write to the Free Software
500 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
502 top_builddir = @top_builddir@
503 MKDIR_P = @MKDIR_P@
505 DESKTOP_I18N_LOCALE_DIR = @DESKTOP_I18N_LOCALE_DIR@
507 # We need to temporarily install the localisation files somewhere so
508 # that desktop-i18n --create can look up messages in them. We recreate
509 # the timestamp before *and* after running 'make install' so that the
510 # make doesn't go into an infinite loop!
511 all: stamp-i18n
512 stamp-i18n: \$(DESKTOP_I18N_LOCALE_DIR) stamp-po Makefile
513 @echo timestamp > stamp-i18nT && mv stamp-i18nT stamp-i18n
514 \$(MAKE) prefix=\$(DESKTOP_I18N_LOCALE_DIR) install && \
515 cp \$(srcdir)/LINGUAS \$(DESKTOP_I18N_LOCALE_DIR)/\$(DOMAIN).LINGUAS \
516 || rm stamp-i18n
517 @echo timestamp > stamp-i18nT && mv stamp-i18nT stamp-i18n
518 \$(DESKTOP_I18N_LOCALE_DIR):
519 \$(MKDIR_P) \$(DESKTOP_I18N_LOCALE_DIR)
521 clean: clean-i18n
522 clean-i18n:
523 -rm -rf \$(DESKTOP_I18N_LOCALE_DIR) stamp-i18n
525 # End of desktop-i18n rules
526 #####################################################################
529 done
534 # First argument has to be the mode of operation. Then call the
535 # appropriate function to process the rest of the arguments and do the
536 # work.
537 if test -z $1; then usage 1; fi
538 MODE=$1; shift
539 case $MODE in
540 --extract) do_extract "$@";;
541 --create) do_create "$@";;
542 --setup) do_setup "$@";;
543 --help) usage;;
544 *) usage 1;;
545 esac