missing NULL terminator in set_config_x
[geda-gaf.git] / build-tools / desktop-i18n
blob502cd4e14d34c0c10547820c5cf521ff4ae77b42
1 #!/bin/sh
2 # -*-Shell-script-*-
3 # Helper script for translating desktop integration data
4 # Copyright (C) 2009-2010, 2016 Peter Brett <peter@peter-b.co.uk>
5 # Copyright (C) 2010 Dan McMahill <dan@mcmahill.net>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 # usage [EXITSTATUS]
22 # ------------------
23 # Print a simple help message, then exit with EXITSTATUS
24 usage() {
25 cat <<EOF
26 Carry out translation tasks on desktop integration data.
28 Usage:
29 desktop-i18n --extract <options> -- <xgettext_options>
30 desktop-i18n --create <options> INFILE OUTFILE
31 desktop-i18n --setup
33 Operating modes:
34 --extract Extract strings by calling xgettext
35 --create Substitute translated strings
36 --setup Setup a source tree to use desktop-i18n
37 --help Print this message
39 Options for --extract mode:
40 --xgettext=XGETTEXT
41 Specify xgettext executable to use
43 Options for --create mode:
44 --gettext=GETTEXT
45 Specify gettext executable to use
46 --domain=TEXTDOMAIN
47 Retrieve translated bmessages from TEXTDOMAIN
48 --localedir=TEXTDOMAINDIR
49 Retrieve message catalog from TEXTDOMAINDIR
50 --lang=LANG Add a language to translate messages into
52 In order for this to work, all strings to be matched must be on a
53 single line. In a .desktop file, a translatable name-value pair must
54 have the desired name prefixed by an underscore. For example:
56 _Comment=gEDA Schematic Editor
58 In a MIME info file, XML tag pairs where the tagname begins with an
59 underscore are recognized. Both tags must be on the same line, and
60 the tag must be the only XML content on the line. Whitespace at the
61 start of the line before the opening tag is preserved. For example:
63 <_comment>gEDA circuit schematic</_comment>
65 Do not include double-quotes (") or slashes (\) in translatable
66 strings.
67 EOF
68 exit $1
71 # extract_desktop INFILE
72 # ----------------------
73 # Parse desktop file data from standard input and generate C on
74 # standard output. If an error occurs, a message is printed blaming
75 # INFILE.
76 extract_desktop() {
77 # First argument is name of file being processed
78 echo "/* Generated from $1 by desktop-i18n */"
79 echo
80 # Loop over each line of standard input
81 n=0
82 while read REPLY; do
83 n=`expr $n + 1`
84 regexp='^_\([^=]*\)=\(.*\)$'
85 if ! (echo $REPLY | grep $regexp > /dev/null); then
86 continue
88 name=`echo $REPLY | sed -e "s:$regexp:\1:"`
89 msgid=`echo $REPLY | sed -e "s:$regexp:\2:"`
91 # Test for bad characters
92 if (echo $msgid | grep '["\\]' > /dev/null); then
93 echo "$1:$n: msgid contains invalid character" >&2
94 exit 2
97 # Generate output line
98 if test "x$name" != x -a "x$msgid" != x; then
99 echo "_(\"$msgid\");"
100 else
101 echo "$1:$n: name or msgid is empty" >&2
102 exit 2
104 done
108 # extract_xml INFILE
109 # ------------------
110 # Parse XML mimeinfo data from standard input and generate C on
111 # standard output. If an error occurs, a message is printed blaming
112 # INFILE.
113 extract_xml() {
114 echo "/* Generated from $1 by desktop-i18n */"
115 echo
116 # Loop over each line of standard input
118 while read REPLY; do
119 n=`expr $n + 1`
120 regexp='<_\([a-zA-Z][a-zA-Z]*\)>\(.*\)</_\1>'
121 if ! (echo $REPLY | grep $regexp > /dev/null); then
122 continue
124 name=`echo $REPLY | sed -e "s:$regexp:\1:"`
125 msgid=`echo $REPLY | sed -e "s:$regexp:\2:"`
127 # Test for bad characters
128 if (echo $msgid | grep '["\\]' > /dev/null); then
129 echo "$1:$n: msgid contains invalid character" >&2
130 exit 2
133 # Generate output line
134 if test "x$name" != x -a "x$msgid" != x; then
135 echo "_(\"$msgid\");"
136 else
137 echo "$1:$n: name or msgid is empty" >&2
138 exit 2
140 done
143 # do_extract [OPTION]... -- [XGETTEXT_OPTION]...
144 # ----------------------------------------------
145 # A wrapper around xgettext. It identifies the file lists and search
146 # directories being used by xgettext, and from them any desktop or
147 # mimeinfo files to be processed.
149 # It then creates a new private directory, and adds it to the xgettext
150 # search path. It creates a new file list and set of preprocessed
151 # files in that directory, and then calls xgettext (preserving all
152 # other original arguments).
153 do_extract() {
154 XGETTEXT=xgettext
156 # First we have to process the command-line arguments
157 for arg; do
158 # Split into name=value
159 name=`echo $arg | sed -e's:=.*::'`
160 value=`echo $arg | sed -e's:^[^=]*=*::'`
162 if test "X$in_xg_args" = X; then
163 # This is an argument only for this script
164 case $name in
165 --xgettext) XGETTEXT="$value";;
166 --help) usage;;
167 --) in_xg_args=1;;
168 *) usage 1;;
169 esac
171 else
172 # This is an argument to xgettext. Luckily the Makefile only
173 # uses full-length arguments, and we only really care about the
174 # ones from there!
175 case $name in
176 --version)
177 # We need to invoke xgettext with --version and do nothing else
178 exec $XGETTEXT --version;;
180 --directory) search_dirs="$search_dirs $value";;
181 --files-from) file_lists="$file_lists $value";;
182 --default-domain) domain="$value"; set x "$@" "$arg"; shift;;
184 # We just want to pass this arg straight to xgettext, so
185 # stick it back on the end of the positional parameters
186 set x "$@" "$arg"; shift;;
187 esac
190 # Discard processed arg from positional parameters
191 shift
192 done
194 # If our private data directory exists, die. Otherwise, create it.
195 priv_dir=.po-input
196 if test -d $priv_dir; then
197 echo "desktop-i18n: $PWD/$priv_dir already exists"
198 exit 3
200 mkdir $priv_dir
202 # Process file lists if necessary
203 if test "X$file_lists" != X; then
204 # Extract names of files we need to preprocess
205 desktop_in=`cat $file_lists | grep ".desktop.in$"`
206 xml_in=`cat $file_lists | grep ".xml.in$"`
208 # Create a new POTFILES file which uses the postprocessed
209 # filenames instead of the original ones.
210 cat $file_lists | \
211 sed -e "s:.desktop.in$:.desktop.in.h:" -e "s:.xml.in$:.xml.in.h:" \
212 > $priv_dir/POTFILES
215 # Preprocess .desktop files
216 for f in $desktop_in; do
217 src=`_search_file $f $search_dirs` || { rm -rf $priv_dir; exit 3; }
218 mkdir -p $priv_dir/`dirname $f`
219 extract_desktop $f < $src > $priv_dir/$f.h
220 done
222 # Preprocess .xml files
223 for f in $xml_in; do
224 src=`_search_file $f $search_dirs` || { rm -rf $priv_dir; exit 3; }
225 mkdir -p $priv_dir/`dirname $f`
226 extract_xml $f < $src > $priv_dir/$f.h
227 done
229 # Call xgettext (recall we saved some args in $@)
230 gen_args="--files-from=$priv_dir/POTFILES --directory=$priv_dir"
231 for d in $search_dirs; do
232 gen_args="$gen_args --directory=$d"
233 done
234 $XGETTEXT $gen_args "$@"
236 # Fix up file suffixes. Recall that we added .h to the ends of some
237 # filenames -- now we need to remove them from the generated .pot
238 # files.
239 echo "Fixing up $domain.po"
240 sed -i -e "s/.desktop.in.h/.desktop.in/" -e "s/.xml.in.h/.xml.in/" \
241 $domain.po
243 # Clean up private directory
244 rm -rf $priv_dir
247 _search_file() {
248 f=$1
249 shift
250 for d in $@; do
251 if test -f "$d/$f"; then echo "$d/$f"; exit; fi
252 done
253 echo "desktop-i18n: Cannot find $f in xgettext search directories"
256 # create_desktop INFILE
257 # ---------------------
258 # Parse desktop file data from standard input and generate a
259 # translated desktop file on standard output. If an error occurs, a
260 # message is printed blaming INFILE.
261 create_desktop() {
262 # Loop over each line of standard input
264 while read REPLY; do
265 n=`expr $n + 1`
266 regexp='^_\([^=]*\)=\(.*\)$'
267 if ! (echo $REPLY | grep $regexp > /dev/null); then
268 echo $REPLY
269 continue
271 name=`echo $REPLY | sed -e "s:$regexp:\1:"`
272 msgid=`echo $REPLY | sed -e "s:$regexp:\2:"`
274 # Test for bad characters
275 if (echo $msgid | grep '["\\]' > /dev/null); then
276 echo "$1:$n: msgid contains invalid character" >&2
277 exit 2
280 # Generate first output line
281 echo "$name=$msgid"
283 # Generate language-specific output lines
284 for lang in $LINGUAS; do
285 msg=`LANGUAGE=$lang $GETTEXT "$msgid"`
286 # If translated message is unmodified, don't write an output
287 # line
288 if test "x$msg" = x -o "$msg" = "$msgid"; then
289 continue;
292 echo "$name[$lang]=$msg"
293 done
294 done
297 # create_xml INFILE
298 # -----------------
299 # Parse XML mimeinfo data from standard input and generate a
300 # translated mimeinfo file on standard output. If an error occurs, a
301 # message is printed blaming INFILE.
302 create_xml() {
303 # Loop over each line of standard input
305 while : ; do
307 # We have to do an ugly hack to avoid stripping whitespace.
308 saveIFS="$IFS"
309 IFS=
310 read REPLY || { IFS="$saveIFS" ; break; }
311 IFS="$saveIFS"
313 n=`expr $n + 1`
314 regexp='<_\([a-zA-Z][a-zA-Z]*\)>\(.*\)</_\1>'
315 if ! (echo $REPLY | grep $regexp > /dev/null); then
316 echo "$REPLY"
317 continue
319 name=`echo $REPLY | sed -e "s:$regexp:\1:"`
320 msgid=`echo $REPLY | sed -e "s:$regexp:\2:"`
321 prefix=`echo $REPLY | sed -e "s:^\(.*\)<_$name>.*:\1:"`
322 suffix=`echo $REPLY | sed -e "s:.*</_$name>\(.*\):\1:"`
324 # Test for bad characters
325 if (echo $msgid | grep '["\\]' > /dev/null); then
326 echo "$1:$n: msgid contains invalid character" >&2
327 exit 2
330 # Test for non-empty prefix/suffix
331 if test "x$prefix" != x -o "x$suffix" != x; then
332 echo "$1:$n: translatable tag must be alone on line" >&2
333 exit 2
336 # Generate first output line
337 echo "$REPLY" | sed -e "s:<_\($name\)>\(.*\)</_\1>:<\1>\2</\1>:"
339 # Generate language-specific output lines
340 for lang in $LINGUAS; do
341 msg=`LANGUAGE=$lang $GETTEXT "$msgid"`
342 # If translated message is unmodified, don't write an output
343 # line
344 if test "x$msg" = x -o "$msg" = "$msgid"; then
345 continue;
347 echo "$REPLY" | sed -e "s,<_\($name\)>\(.*\)</_\1>,<\1 xml:lang=\"$lang\">$msg</\1>,"
348 done
349 done
352 # do_create [OPTION]... INFILE OUTFILE
353 # ------------------------------------
354 # Substitutes translations into .desktop or mimeinfo files.
355 do_create() {
356 GETTEXT=gettext
358 # First process command-line arguments
359 for arg; do
360 # Split into name=value
361 name=`echo $arg | sed -e's:=.*::'`
362 value=`echo $arg | sed -e's:^[^=]*=*::'`
364 case $name in
365 --gettext) GETTEXT=$value;;
366 --domain) TEXTDOMAIN=$value;;
367 --localedir) TEXTDOMAINDIR=$value;;
368 --lang) LINGUAS="$LINGUAS $value";;
370 # Arg might be a filename, so save it at the end of the
371 # positional parameters
372 set x "$@" "$arg"; shift
373 esac
375 # Discard processed arg from positional parameters
376 shift
377 done
379 if test $# != 2; then usage 1; fi # Should only have 2 args left
380 INFILE=$1; OUTFILE=$2
381 if ! test -r $INFILE; then
382 echo "desktop-i18n: Cannot open $INFILE for reading."
383 exit 3
386 export TEXTDOMAIN
387 export GETTEXT
388 export TEXTDOMAINDIR
389 export LINGUAS
391 if (echo "$INFILE" | grep ".desktop.in$" > /dev/null); then
392 create_desktop $INFILE < $INFILE > $OUTFILE
393 exit 0
396 if (echo "$INFILE" | grep ".xml.in$" > /dev/null); then
397 create_xml $INFILE < $INFILE > $OUTFILE
398 exit 0
401 echo "desktop-i18n: $INFILE: Unrecognized extension"
402 exit 1
405 # do_setup [DIR]
406 # --------------
407 # Try to set up a source tree to use desktop-i18n.
409 # This is a nasty bit of hackery. We need to insert some rules into
410 # the Makefile.in.in installed by gettextize/autopoint so that make
411 # knows how to generate input for xgettext.
413 # Unfortunately, there's no nice way to do this, so we do it by
414 # appending some rules onto each Makefile.in.in, using the following
415 # procedure:
417 # 1. Look for configure.ac in DIR, or in cwd if DIR wasn't
418 # specified. If we can't find it, whinge.
419 # 2. If configure.ac doesn't have AX_DESKTOP_I18N, quit successfully.
420 # 3. Find anywhere where AC_CONFIG_FILES is called. For each
421 # Makefile.in found in the list of files to create:
422 # (a) Check for Makefile.in.in. If it doesn't exist, skip with a warning.
423 # (b) If Makefile.in.in contains the string DESKTOP_I18N_RULES,
424 # skip silently.
425 # (c) Append a chunk of rules onto Makefile.in.in
427 # Note that we can't use a po/Rules-* file because substitution is not
428 # carried out on these files.
429 do_setup() {
430 # Was DIR specified?
431 if test "x$1" = x; then srcdir=.; else srcdir=$1; fi
433 # Can we find configure.ac or configure.in?
434 for f in configure.ac configure.in; do
435 if test -r $srcdir/$f; then
436 ac_file=$srcdir/$f
437 break
439 done
440 if test "x$ac_file" = x; then
441 echo "Cannot find configure.ac or configure.in!"
442 exit 4
445 # Check that configure.ac is readable
446 if ! test -r $ac_file; then
447 echo "Cannot open $ac_file for reading."
448 exit 3
451 # Is the AX_DESKTOP_I18N macro present?
452 if ! grep AX_DESKTOP_I18N $ac_file > /dev/null; then
453 exit
456 # Now we use a piece of m4 code to try and discover all of the
457 # configuration files. This is UGLY AND BAD, because it only detects
458 # when AC_CONFIG_FILES is called in the main configure script (if
459 # AC_CONFIG_FILES is called by another macro somewhere, it won't be
460 # detected).
461 cat - $ac_file > conftest <<EOF
462 changequote([,])dnl
463 divert([-1])
464 define([AC_CONFIG_FILES], [divert([0])[\$1]divert([-1])])
466 conf_files=`m4 conftest`
467 rm conftest
469 # Look for any files called Makefile.in.
470 for f in $conf_files; do
471 # Discard any composition rules and prepend srcdir.
472 f=`echo "$f" | sed -e 's,:.*,,'`
473 f="$srcdir/$f"
475 # Skip files not called Makefile.in
476 if test `echo "$f" | sed -e 's:.*/::'` != Makefile.in; then
477 continue
480 # Check that a corresponding Makefile.in.in exists and we can
481 # read/write it
482 if ! test -r $f.in -a -w $f.in; then
483 echo "desktop-i18n: Cannot process $f.in"
484 continue
487 # Check that we haven't already hacked it
488 if grep DESKTOP_I18N_RULES $f.in > /dev/null; then
489 continue
492 # Append our rules
493 echo "desktop-i18n: modifying $f.in"
494 cat >> $f.in <<EOF
496 # DESKTOP_I18N_RULES (Do not edit or remove this line)
497 #####################################################################
498 # Makefile rules needed by the desktop-i18n tool.
499 # Copyright (C) 2009-2010 Peter Brett <peter@peter-b.co.uk>
500 # Copyright (C) 2010 Dan McMahill <dan@mcmahill.net>
502 # This program is free software; you can redistribute it and/or modify
503 # it under the terms of the GNU General Public License as published by
504 # the Free Software Foundation; either version 2 of the License, or
505 # (at your option) any later version.
507 # This program is distributed in the hope that it will be useful,
508 # but WITHOUT ANY WARRANTY; without even the implied warranty of
509 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
510 # GNU General Public License for more details.
512 # You should have received a copy of the GNU General Public License
513 # along with this program; if not, write to the Free Software
514 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
516 top_builddir = @top_builddir@
517 MKDIR_P = @MKDIR_P@
519 DESKTOP_I18N_LOCALE_DIR = @DESKTOP_I18N_LOCALE_DIR@
521 # We need to temporarily install the localisation files somewhere so
522 # that desktop-i18n --create can look up messages in them. We recreate
523 # the timestamp before *and* after running 'make install' so that the
524 # make doesn't go into an infinite loop!
525 all: stamp-i18n
526 stamp-i18n: \$(DESKTOP_I18N_LOCALE_DIR) stamp-po Makefile
527 @echo timestamp > stamp-i18nT && mv -f stamp-i18nT stamp-i18n
528 \$(MAKE) \
529 prefix=\$(DESKTOP_I18N_LOCALE_DIR) \
530 localedir=\$(DESKTOP_I18N_LOCALE_DIR)/share/locale \
531 DESTDIR= install && \
532 cp \$(srcdir)/LINGUAS \$(DESKTOP_I18N_LOCALE_DIR)/\$(DOMAIN).LINGUAS \
533 || rm stamp-i18n
534 @echo timestamp > stamp-i18nT && mv -f stamp-i18nT stamp-i18n
535 \$(DESKTOP_I18N_LOCALE_DIR):
536 \$(MKDIR_P) \$(DESKTOP_I18N_LOCALE_DIR)
538 clean: clean-i18n
539 clean-i18n:
540 -rm -rf \$(DESKTOP_I18N_LOCALE_DIR) stamp-i18n
542 # End of desktop-i18n rules
543 #####################################################################
546 done
551 # First argument has to be the mode of operation. Then call the
552 # appropriate function to process the rest of the arguments and do the
553 # work.
554 if test -z $1; then usage 1; fi
555 MODE=$1; shift
556 case $MODE in
557 --extract) do_extract "$@";;
558 --create) do_create "$@";;
559 --setup) do_setup "$@";;
560 --help) usage;;
561 *) usage 1;;
562 esac