3 # Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 # Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 # Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 # Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 # Copyright (C) 2006 - 2008 by Daniel Stenberg
13 # binary version for the binary lang file
14 my $langversion = 4; # 3 was the latest one used in the v1 format
16 # A note for future users and readers: The original v1 language system allowed
17 # the build to create and use a different language than english built-in. We
18 # removed that feature from our build-system, but the build scripts still had
19 # the ability. But, starting now, this ability is no longer provided since I
20 # figured it was boring and unnecessary to write support for now since we
21 # don't use it anymore.
25 Usage: genlang [options] <langv2 file>
28 Make the tool create a [prefix].c and [prefix].h file.
31 Make the tool create a binary language (.lng) file named [outfile].
32 The use of this option requires that you also use -e, -t and -i.
35 Update language file. Given the translated file and the most recent english
36 file, you\'ll get an updated version sent to stdout. Suitable action to do
37 when you intend to update a translation.
39 -e=<english lang file>
40 Point out the english (original source) file, to use that as master
41 language template. Used in combination with -b, -u or -s.
44 Sort the Update language file in the same order as the strings in the
48 Specify which target you want the translations/phrases for. Required when
51 The target can in fact be specified as numerous different strings,
52 separated with colons. This will make genlang to use all the specified
53 strings when searching for a matching phrase.
56 The target id number, needed for -b.
59 Voice mode output. Outputs all id: and voice: lines for the given target!
62 Enables verbose (debug) output.
70 # 1) scan the english file, keep the whole <phrase> for each phrase.
71 # 2) read the translated file, for each end of phrase, compare:
72 # A) all source strings, if there's any change there should be a comment about
76 # 3) output the phrase with the comments from above
77 # 4) check which phrases that the translated version didn't have, and spit out
78 # the english version of those
89 my $check = ($binary?
1:0) + ($prefix?
1:0) + ($update?
1:0) + ($voiceout?
1:0) + ($sortfile?
1:0);
92 print "Please use only one of -p, -u, -o, -b and -s\n";
96 print "Please use at least one of -p, -u, -o, -b and -s\n";
101 if(($binary || $update || $voiceout || $sortfile) && !$english) {
102 print "Please use -e too when you use -b, -o, -u or -s\n";
107 if($binary && !$target_id) {
108 print "Please specify a target id number (with -i)!\n";
113 if(!$target && !$update && !$sortfile) {
114 print "Please specify a target (with -t)!\n";
119 my %id; # string to num hash
120 my @idnum; # num to string array
122 my %allphrases; # For sorting - an array of the <phrase> elements
123 my %source; # id string to source phrase hash
124 my %dest; # id string to dest phrase hash
125 my %voice; # id string to voice phrase hash
127 my $input = $ARGV[0];
140 my ($string, $pattern)=@_;
142 $pattern =~ s/\*/.?*/g;
143 $pattern =~ s/\?/./g;
145 return ($string =~ /^$pattern\z/);
154 my ($full, $n, $v)=@_;
160 my ($full, $n, $v)=@_;
165 my ($debug, $strref, $full, $n, $v)=@_;
167 my @all= split(" *, *", $n);
170 # print "TEST ($debug) $target for $test\n";
171 for my $part (split(":", $target)) {
172 if(match
($part, $test)) {
174 # print "MATCH: $test => $v\n";
184 parsetarget
("src", \
$src, @_);
189 parsetarget
("dest", \
$dest, @_);
194 parsetarget
("voice", \
$voice, @_);
200 # For the cases where the english file needs to be scanned/read, we do
201 # it before we read the translated file. For -b it isn't necessary, but for
202 # -u it is convenient.
204 my $idnum=0; # start with a true number
205 my $vidnum=0x8000; # first voice id
206 open(ENG
, "<$english") || die "Error: can't open $english";
214 # get rid of DOS newlines
217 if($_ =~ /^ *\<phrase\>/) {
218 # this is the start of a phrase
220 elsif($_ =~ /^ *\<\/phrase\
>/) {
222 # if id is something, when we count and store this phrase
224 # voice-only entries get a difference range
225 if($id =~ /^VOICE_/) {
226 # Assign an ID number to this entry
231 # Assign an ID number to this entry
234 # print STDERR "DEST: bumped idnum to $idnum\n";
237 # this is the end of a phrase, add it to the english hash
238 $english{$id}=join("", @phrase);
244 # gather everything related to this phrase
246 if($_ =~ /^ *\<dest\>/i) {
250 elsif($withindest && ($_ =~ /^ *\<\/dest\
>/i
)) {
253 if($update || ($deststr && ($deststr !~ /^none\z/i))) {
254 # we unconditionally always use all IDs when the "update"
257 # print "DEST: use this id $id\n";
260 # print "skip $maybeid for $name\n";
263 elsif($withindest && ($_ =~ / *([^:]+): *(.*)/)) {
264 my ($name, $val)=($1, $2);
265 $dest=""; # in case it is left untouched for when the
266 # model name isn't "our"
267 dest
($_, $name, $val);
270 # Store the current dest string. If this target matches
271 # multiple strings, it will get updated several times.
277 if($_ =~ /^ *id: ([^ \t\n]+)/i) {
279 $sortorder{$maybeid}=$numphrases++;
285 # a function that compares the english phrase with the translated one.
286 # compare source strings and desc
288 # Then output the updated version!
290 my ($idstr, $engref, $locref)=@_;
292 my ($esource, $lsource);
295 for my $l (@
$engref) {
300 if($l =~ /^ *desc: (.*)/) {
303 elsif($l =~ / *\<source\>/i) {
307 if($l =~ / *\<\/source\
>/i
) {
318 for my $l (@
$locref) {
319 if($l =~ /^ *desc: (.*)/) {
321 if(trim
($edesc) ne trim
($ldesc)) {
322 $l = "### The 'desc' field differs from the english!\n### the previously used desc is commented below:\n### desc: $ldesc\n desc: $edesc\n";
326 elsif($l =~ / *\<source\>/i) {
331 if($l =~ / *\<\/source\
>/i
) {
334 if(trim
($esource) ne trim
($lsource)) {
335 print "### The <source> section differs from the english!\n",
336 "### the previously used one is commented below:\n";
337 for(split("\n", $lsource)) {
345 undef @show; # start over
362 my $idcount; # counter for lang ID numbers
363 my $voiceid=0x8000; # counter for voice-only ID numbers
366 # Now start the scanning of the selected language string
369 open(LANG
, "<$input") || die "Error: couldn't read language file named $input\n";
376 # get rid of DOS newlines
379 if($_ =~ /^( *\#|[ \t\n\r]*\z)/) {
380 # comment or empty line - output it if it's part of the header
381 if ($header and ($update || $sortfile)) {
394 # this is an XML-lookalike tag
395 if (/^(<|[^\"<]+<)([^>]*)>/) {
397 # print "P: $part\n";
400 # this was a closing tag
402 if($part eq "/phrase") {
405 my $idstr = $phrase{'id'};
408 if($binary && !$english{$idstr}) {
409 # $idstr doesn't exist for english, skip it\n";
411 elsif($dest =~ /^none\z/i) {
412 # "none" as dest (without quotes) means that this entire
413 # phrase is to be ignored
416 $allphrases{$idstr}=join('',@phrase);
419 # we don't do the fully detailed analysis when we "update"
420 # since we don't do it for a particular target etc
422 # allow the keyword 'deprecated' to be used on dest and
423 # voice strings to mark that as deprecated. It will then
424 # be replaced with "".
426 $dest =~ s/^deprecate(|d)\z/\"\"/i;
427 $voice =~ s/^deprecate(|d)\z/\"\"/i;
429 # basic syntax error alerts, if there are no quotes we
430 # will assume an empty string was intended
432 print STDERR
"$input:$line:1: warning: dest before line lacks quotes ($dest)!\n";
436 print STDERR
"$input:$line:1: warning: source before line lacks quotes ($src)!\n";
439 if($voice !~ /^\"/ and $voice !~ /^none\z/i) {
440 print STDERR
"$input:$line:1: warning: voice before line lacks quotes ($voice)!\n";
444 # Use the ID name to figure out which id number range we
445 # should use for this phrase. Voice-only strings are
448 if($idstr =~ /^VOICE/) {
455 $id{$idstr} = $idnum;
456 $idnum[$idnum]=$idstr;
458 $source{$idstr}=$src;
460 $voice{$idstr}=$voice;
463 print "id: $phrase{id} ($idnum)\n";
464 print "source: $src\n";
465 print "dest: $dest\n";
466 print "voice: $voice\n";
476 my $e = $english{$idstr};
479 # compare original english with this!
480 my @eng = split("\n", $english{$idstr});
482 compare
($idstr, \
@eng, \
@phrase);
484 $english{$idstr}=""; # clear it
487 print "### $idstr: The phrase is not used. Skipped\n";
494 # starts with a slash, this _ends_ this section
495 $m = pop @m; # get back old value, the previous level's tag
499 # This is an opening (sub) tag
501 push @m, $m; # store old value
506 if(/^ *([^:]+): *(.*)/) {
507 my ($name, $val)=($1, $2);
508 &$m($_, $name, $val);
518 "### This phrase below was not present in the translated file\n",
527 for(sort { $sortorder{$a} <=> $sortorder{$b} } keys %allphrases) {
528 print $allphrases{$_};
533 # We create a .c and .h file
535 open(HFILE
, ">$prefix.h") ||
536 die "Error: couldn't create file $prefix.h\n";
537 open(CFILE
, ">$prefix.c") ||
538 die "Error: couldn't create file $prefix.c\n";
541 /* This file was automatically generated using genlang */
543 * The str() macro/functions is how to access strings that might be
544 * translated. Use it like str(MACRO) and expect a string to be
547 #define str(x) language_strings[x]
549 /* this is the array for holding the string pointers.
550 It will be initialized at runtime. */
551 extern unsigned char *language_strings[];
552 /* this contains the concatenation of all strings, separated by \\0 chars */
553 extern const unsigned char language_builtin[];
555 /* The enum below contains all available strings */
561 /* This file was automaticly generated using genlang, the strings come
566 unsigned char *language_strings[LANG_LAST_INDEX_IN_ARRAY];
567 const unsigned char language_builtin[] =
571 # Output the ID names for the enum in the header file
573 for $i (1 .. $idcount) {
574 my $name=$idnum[$i - 1]; # get the ID name
576 $name =~ s/\"//g; # cut off the quotes
578 printf HFILE
(" %s, /* %d */\n", $name, $i-1);
581 # Output separation marker for last string ID and the upcoming voice IDs
584 LANG_LAST_INDEX_IN_ARRAY, /* this is not a string, this is a marker */
585 /* --- below this follows voice-only strings --- */
586 VOICEONLY_DELIMITER = 0x8000,
590 # Output the ID names for the enum in the header file
591 for $i (0x8000 .. ($voiceid-1)) {
592 my $name=$idnum[$i]; # get the ID name
594 $name =~ s/\"//g; # cut off the quotes
596 printf HFILE
(" %s,\n", $name);
600 print HFILE
"\n};\n/* end of generated enum list */\n";
602 # Output the target phrases for the source file
603 for $i (1 .. $idcount) {
604 my $name=$idnum[$i - 1]; # get the ID
605 my $dest = $dest{$name}; # get the destination phrase
607 $dest =~ s
:\"$:\\0\":; # insert a \0 before the second quote
610 # this is just to be on the safe side
614 printf CFILE
(" %s\n", $dest);
617 # Output end of string chunk
620 /* end of generated string list */
626 } # end of the c/h file generation
628 # Creation of a binary lang file was requested
630 # We must first scan the english file to get the correct order of the id
631 # numbers used there, as that is what sets the id order for all language
632 # files. The english file is scanned before the translated file was
635 open(OUTF
, ">$binary") or die "Error: Can't create $binary";
637 printf OUTF
("\x1a%c%c", $langversion, $target_id); # magic lang file header
639 # loop over the target phrases
640 for $i (1 .. $idcount) {
641 my $name=$idnum[$i - 1]; # get the ID
642 my $dest = $dest{$name}; # get the destination phrase
645 $dest =~ s/^\"(.*)\"\s*$/$1/g; # cut off quotes
647 # Now, make sure we get the number from the english sort order:
648 $idnum = $idmap{$name};
650 printf OUTF
("%c%c%s\x00", ($idnum>>8), ($idnum&0xff), $dest);
655 # voice output requested, display id: and voice: strings in a v1-like
660 # This loops over the strings in the translated language file order
661 my @ids = ((0 .. ($idcount-1)));
662 push @ids, (0x8000 .. ($voiceid-1));
669 my $name=$idnum[$i]; # get the ID
670 my $dest = $voice{$name}; # get the destination voice string
673 $dest =~ s/^\"(.*)\"\s*$/$1/g; # cut off quotes
675 # Now, make sure we get the number from the english sort order:
676 $idnum = $idmap{$name};
681 #print "Input index $i output index $idnum\n";
684 # not used, mark it so
694 if(($o < 0) || !length($o)) {
695 print "#$i\nid: NOT_USED_$i\nvoice: \"\"\n";
699 my $name=$idnum[$o]; # get the ID
700 my $dest = $voice{$name}; # get the destination voice string
702 print "#$i ($o)\nid: $name\nvoice: $dest\n";
709 printf("%d ID strings scanned\n", $idcount);
713 printf "$_: %s\n", $head{$_};