summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSolomon Peachy <pizza@shaftnet.org>2020-07-27 01:10:34 -0400
committerSolomon Peachy <pizza@shaftnet.org>2020-07-27 14:58:38 -0400
commit2305966d847f9891d9b5589abd8c52a176d3c3fa (patch)
treefd6a046a1716f9b974f266f997f404681ecb42d2
parentc81e1e1bf1addf05af62d14047fad3a3cfb4c76c (diff)
downloadrockbox-2305966d847f9891d9b5589abd8c52a176d3c3fa.tar.gz
rockbox-2305966d847f9891d9b5589abd8c52a176d3c3fa.zip
updatelang: New tool to update language files.
Change-Id: I3c18bb34770b4b4b321199149a2ea693dfbdb7f4
-rw-r--r--apps/lang/lang.make6
-rwxr-xr-xtools/updatelang496
-rwxr-xr-xtools/voice.pl2
3 files changed, 500 insertions, 4 deletions
diff --git a/apps/lang/lang.make b/apps/lang/lang.make
index c5db820326..807ac0f53f 100644
--- a/apps/lang/lang.make
+++ b/apps/lang/lang.make
@@ -21,7 +21,7 @@ CLEANOBJS += $(BUILDDIR)/lang/max_language_size.h $(BUILDDIR)/lang/lang*
21#DUMMY := $(shell mkdir -p $(BUILDDIR)/apps/lang) 21#DUMMY := $(shell mkdir -p $(BUILDDIR)/apps/lang)
22 22
23# Calculate the maximum language size. Currently based on the file size 23# Calculate the maximum language size. Currently based on the file size
24# of the largest lng file. Subtract 10 due to HEADER_SIZE and 24# of the largest lng file. Subtract 10 due to HEADER_SIZE and
25# SUBHEADER_SIZE. 25# SUBHEADER_SIZE.
26# TODO: In the future generate this file within genlang or another script 26# TODO: In the future generate this file within genlang or another script
27# in order to only calculate the maximum size based on the core strings. 27# in order to only calculate the maximum size based on the core strings.
@@ -47,10 +47,10 @@ $(BUILDDIR)/lang_enum.h: $(BUILDDIR)/lang/lang.h
47 47
48# NOTE: for some weird reasons in GNU make, multi targets rules WITH patterns actually express 48# NOTE: for some weird reasons in GNU make, multi targets rules WITH patterns actually express
49# the fact that the two files are created as the result of one invocation of the rule 49# the fact that the two files are created as the result of one invocation of the rule
50$(BUILDDIR)/%.lng $(BUILDDIR)/%.vstrings: $(ROOTDIR)/%.lang $(BUILDDIR)/apps/genlang-features 50$(BUILDDIR)/%.lng $(BUILDDIR)/%.vstrings: $(ROOTDIR)/%.lang $(BUILDDIR)/apps/genlang-features $(TOOLSDIR)/genlang $(TOOLSDIR)/updatelang
51 $(call PRINTS,GENLANG $(subst $(ROOTDIR)/,,$<)) 51 $(call PRINTS,GENLANG $(subst $(ROOTDIR)/,,$<))
52 $(SILENT)mkdir -p $(dir $@) 52 $(SILENT)mkdir -p $(dir $@)
53 $(SILENT)$(TOOLSDIR)/genlang -u -e=$(APPSDIR)/lang/$(ENGLISH).lang $< > $@.tmp 53 $(SILENT)$(TOOLSDIR)/updatelang $(APPSDIR)/lang/$(ENGLISH).lang $< $@.tmp
54 $(SILENT)$(TOOLSDIR)/genlang -e=$(APPSDIR)/lang/$(ENGLISH).lang -t=$(MODELNAME):`cat $(BUILDDIR)/apps/genlang-features` -i=$(TARGET_ID) -b=$*.lng -c=$*.vstrings $@.tmp 54 $(SILENT)$(TOOLSDIR)/genlang -e=$(APPSDIR)/lang/$(ENGLISH).lang -t=$(MODELNAME):`cat $(BUILDDIR)/apps/genlang-features` -i=$(TARGET_ID) -b=$*.lng -c=$*.vstrings $@.tmp
55 $(SILENT)rm -f $@.tmp 55 $(SILENT)rm -f $@.tmp
56 56
diff --git a/tools/updatelang b/tools/updatelang
new file mode 100755
index 0000000000..a139bc77f8
--- /dev/null
+++ b/tools/updatelang
@@ -0,0 +1,496 @@
1#!/usr/bin/perl -s -w
2# __________ __ ___.
3# Open \______ \ ____ ____ | | _\_ |__ _______ ___
4# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7# \/ \/ \/ \/ \/
8#
9# Copyright (C) 2020 Solomon Peachy
10#
11
12use Clone 'clone';
13use utf8;
14use File::Basename;
15
16sub trim {
17 my ($string) = @_;
18 $string =~ s/^\s+//;
19 $string =~ s/\s+$//;
20 return $string;
21}
22
23sub parselangfile {
24 my ($filename) = @_;
25 my %phrases;
26 my @order;
27 my %empty = ( #'phrase' => {},
28 #'source' => {},
29 #'dest' => {},
30 #'voice' => {},
31 'notes' => "",
32 'new' => 0
33 );
34 my %thisphrase = %empty;
35
36 open(FH, "<$filename") || die ("Can't open $filename");
37 my @lines = <FH>;
38 close(FH);
39
40 my $pos = 'lang';
41 my $id = '';
42 my @comments;
43
44 foreach my $line (@lines) {
45 $line = trim($line);
46 if($line =~ /^ *#/) {
47 push(@comments, "$line\n") if ($pos eq 'lang');
48 # comments are ignored!
49 next;
50 } elsif ($pos eq 'phrase' && $line =~ /^([^:]+): ?(.*)$/) {
51 $thisphrase{$pos}->{$1} = $2;
52 if ($1 eq 'id') {
53 push(@order, $2);
54 $id = $2;
55 }
56 } elsif ($pos ne 'phrase' && $line =~ /^([^:]+): ?\"?([^\"]*)\"?$/) {
57 my @targets = split(',', $1);
58 foreach (@targets) {
59 my $l = trim($_);
60 $thisphrase{$pos}->{$l} = $2;
61 }
62 }
63 if ($line eq '</voice>' ||
64 $line eq '</dest>' ||
65 $line eq '</source>' ||
66 $line eq '<phrase>') {
67 $pos = 'phrase';
68 } elsif ($line eq '</phrase>') {
69 $phrases{$id} = clone(\%thisphrase);
70 %thisphrase = %empty;
71 $pos = 'lang';
72 $id = '';
73 } elsif ($line eq '<source>') {
74 $pos = 'source';
75 } elsif ($line eq '<dest>') {
76 $pos = 'dest';
77 } elsif ($line eq '<voice>') {
78 $pos = 'voice';
79 }
80 }
81 $phrases{'HEADER'} = \@comments;
82 $phrases{'ORDER'} = \@order;
83 return %phrases;
84}
85
86sub combinetgts {
87 my (%tgtmap) = (@_);
88 my %strmap;
89 my %combined;
90
91 # Reverse-map things
92 foreach my $tgt (sort(keys(%tgtmap))) {
93 next if ($tgt eq '*'); # Do not combine anything with fallback
94 if (defined($strmap{$tgtmap{$tgt}})) {
95 $strmap{$tgtmap{$tgt}} .= ",$tgt";
96 } else {
97 $strmap{$tgtmap{$tgt}} = "$tgt";
98 }
99 }
100
101 # Copy over default/fallback as it was skipped
102 $combined{'*'} = $tgtmap{'*'};
103
104 foreach my $str (keys(%strmap)) {
105 $combined{$strmap{$str}} = $str;
106 }
107
108 return %combined;
109}
110
111my @ignorelist = split("\n",
112"LANG_SERIAL_BITRATE_19200
113LANG_SERIAL_BITRATE_9600
114LANG_SERIAL_BITRATE_38400
115LANG_SERIAL_BITRATE_57600
116LANG_COMPRESSOR_RATIO_10
117LANG_COMPRESSOR_RATIO_2
118LANG_COMPRESSOR_RATIO_6
119LANG_COMPRESSOR_RATIO_4
120LANG_ROCKBOX_TITLE
121LANG_EQUALIZER_BAND_Q
122LANG_FM_DEFAULT_PRESET_NAME
123LANG_DISK_NAME_MMC
124LANG_COLOR_RGB_LABELS
125LANG_BYTE
126LANG_KIBIBYTE
127LANG_GIBIBYTE
128LANG_USB_HID
129VOICE_ZERO
130VOICE_ONE
131VOICE_TWO
132VOICE_THREE
133VOICE_FOUR
134VOICE_FIVE
135VOICE_SIX
136VOICE_SEVEN
137VOICE_EIGHT
138VOICE_NINE
139VOICE_TEN
140VOICE_ELEVEN
141VOICE_TWELVE
142VOICE_THIRTEEN
143VOICE_FOURTEEN
144VOICE_FIFTEEN
145VOICE_SIXTEEN
146VOICE_SEVENTEEN
147VOICE_EIGHTEEN
148VOICE_NINETEEN
149VOICE_TWENTY
150VOICE_THIRTY
151VOICE_FORTY
152VOICE_FIFTY
153VOICE_SIXTY
154VOICE_SEVENTY
155VOICE_EIGHTY
156VOICE_NINETY
157VOICE_CHAR_A
158VOICE_CHAR_B
159VOICE_CHAR_C
160VOICE_CHAR_D
161VOICE_CHAR_E
162VOICE_CHAR_F
163VOICE_CHAR_G
164VOICE_CHAR_H
165VOICE_CHAR_I
166VOICE_CHAR_J
167VOICE_CHAR_K
168VOICE_CHAR_L
169VOICE_CHAR_M
170VOICE_CHAR_N
171VOICE_CHAR_O
172VOICE_CHAR_P
173VOICE_CHAR_Q
174VOICE_CHAR_R
175VOICE_CHAR_S
176VOICE_CHAR_T
177VOICE_CHAR_U
178VOICE_CHAR_V
179VOICE_CHAR_W
180VOICE_CHAR_X
181VOICE_CHAR_Y
182VOICE_CHAR_Z
183VOICE_PAUSE");
184
185sub not_ignorelist {
186 my ($key) = @_;
187 foreach (@ignorelist) {
188 if ($_ eq $key) {
189 return 0;
190 }
191 }
192 return 1;
193}
194##################
195
196if($#ARGV != 2) {
197 print "Usage: updatelang <english.lang> <otherlang> [<outfile>|-]\n";
198 exit;
199}
200
201# Parse master file
202my %english = parselangfile($ARGV[0]);
203my @englishorder = @{$english{'ORDER'}};
204
205# Parse secondary file
206my %lang = parselangfile($ARGV[1]);
207my @langorder = @{$lang{'ORDER'}};
208my @langheader = @{$lang{'HEADER'}};
209
210# Clean up
211delete $english{'ORDER'};
212delete $english{'HEADER'};
213delete $lang{'ORDER'};
214delete $lang{'HEADER'};
215
216# ork out the missing phrases
217my %missing;
218my @missingorder;
219
220foreach (@englishorder) {
221 $missing{$_} = 1;
222}
223foreach (@langorder) {
224 if (!defined($english{$_})) {
225 delete($lang{$_});
226# print "#!! '$_' no longer needed\n";
227 next;
228 }
229 delete $missing{$_};
230}
231foreach (@englishorder) {
232 push(@missingorder, $_) if defined($missing{$_});
233}
234# And add them to the phrase list.
235foreach (@missingorder) {
236# print "#!! '$_' missing\n";
237 push(@langorder, $_);
238 $lang{$_} = $english{$_};
239 $lang{$_}{'notes'} .= "### This phrase is missing entirely, copying from english!\n";
240 $lang{$_}{'new'} = 1;
241}
242undef @missingorder;
243undef %missing;
244
245# Sanity-check a few things
246foreach my $id (@langorder) {
247 if (!defined($english{$id})) {
248 next;
249 }
250 my %ep = %{$english{$id}{'phrase'}};
251 my %lp = %{$lang{$id}{'phrase'}};
252
253 if ($lp{'desc'} ne $ep{'desc'}) {
254 if ($ep{'desc'} eq 'deprecated') {
255 # Nuke all deprecated targets; just copy from English
256# print "#!! '$id' deprecated, deleting\n";
257 $lang{$id} = $english{$id};
258 } else {
259 $lang{$id}{'notes'} .= "### The 'desc' field for '$id' differs from the english!\n### the previously used desc is commented below:\n### desc: $lp{desc}\n";
260 $lang{$id}{'phrase'}{'desc'} = $english{$id}{'phrase'}{'desc'};
261 # print "#!! '$id' changed description\n";
262 }
263 }
264
265 if (!defined($lp{'user'}) || $lp{'user'} ne $ep{'user'}) {
266 if (!defined($lp{'user'})) {
267 $lp{'user'} = $ep{'user'};
268 }
269 $lang{$id}{'notes'} .= "### The 'user' field for '$id' differs from the english!\n### the previously used desc is commented below:\n### desc: $lp{user}\n";
270 $lang{$id}{'phrase'}{'user'} = $english{$id}{'phrase'}{'user'};
271# print "#!! '$id' changed user\n";
272 }
273}
274
275# Check sources
276foreach my $id (@langorder) {
277 if (!defined($english{$id})) {
278 next;
279 }
280 my %ep = %{$english{$id}{'source'}};
281 my %lp;
282
283 if (defined($lang{$id}{'source'})) {
284 %lp = %{$lang{$id}{'source'}};
285 } else {
286 %lp = ();
287 }
288
289 foreach my $tgt (keys(%lp)) {
290 if (!defined($ep{$tgt})) {
291 # Delete any targets that have been nuked in master
292 delete($lang{$id}{'source'}{$tgt});
293 }
294 }
295 foreach my $tgt (keys(%ep)) {
296 if (!defined($lp{$tgt})) {
297 # If it doesn't exist in the language, copy it from English
298 $lang{$id}{'notes'} .= "### The <source> section for '$id:$tgt' is missing! Copying from english!\n";
299# print "#!! '$id:$tgt' source missing\n";
300 $lang{$id}{'source'}{$tgt} = $english{$id}{'source'}{$tgt};
301 } elsif ($lp{$tgt} ne $ep{$tgt}) {
302 # If the source string differs, complain, and copy from English
303 $lang{$id}{'notes'} .= "### The <source> section for '$id:$tgt' differs from the english!\n";
304 $lang{$id}{'notes'} .= "### the previously used one is commented below:\n";
305 $lang{$id}{'notes'} .= "### $english{$id}{source}{$tgt}\n";
306# print "#!! '$id:$tgt' source changed ('$lp{$tgt}' vs '$ep{$tgt}')\n";
307 $lang{$id}{'source'}{$tgt} = $english{$id}{'source'}{$tgt};
308 }
309 }
310}
311
312# Check dests
313foreach my $id (@langorder) {
314 if (!defined($english{$id})) {
315 next;
316 }
317 my %ep = %{$english{$id}{'dest'}};
318 my %lp;
319
320 if (defined($lang{$id}{'dest'})) {
321 %lp = %{$lang{$id}{'dest'}};
322 } else {
323 %lp = ();
324 }
325
326 foreach my $tgt (keys(%lp)) {
327 if (!defined($ep{$tgt})) {
328 # Delete any targets that have been nuked in master
329 delete($lang{$id}{'dest'}{$tgt});
330 }
331 }
332 foreach my $tgt (keys(%ep)) {
333 if (!defined($lp{$tgt})) {
334 # If it doesn't exist in the language, copy it from English
335 $lang{$id}{'notes'} .= "### The <dest> section for '$id:$tgt' is missing! Copying from english\n";
336# print "#!! '$id:$tgt' dest missing\n";
337 $lang{$id}{'dest'}{$tgt} = $english{$id}{'dest'}{$tgt};
338 } elsif ($lp{$tgt} ne $ep{$tgt}) {
339 # If the source string differs, complain, and copy from English
340 if ($lp{$tgt} eq '' && $ep{$tgt} ne '') {
341 $lang{$id}{'notes'} .= "### The <dest> section for '$id:$tgt' is blank! Copying from english!\n";
342# print "#!! '$id:$tgt' dest is blank ('$lp{$tgt}' vs '$ep{$tgt}')\n";
343 $lang{$id}{'source'}{$tgt} = $english{$id}{'source'}{$tgt};
344 } elsif ($lp{$tgt} ne '' && $ep{$tgt} eq '') {
345 # It should be kept blank!
346 $lang{$id}{'notes'} .= "### The <dest> section for '$id:$tgt' is not blank!\n";
347 $lang{$id}{'notes'} .= "### the previously used one is commented below:\n";
348 $lang{$id}{'notes'} .= "### $english{$id}{dest}{$tgt}\n";
349# print "#!! '$id:$tgt' dest not blank ('$lp{$tgt}' vs '$ep{$tgt}')\n";
350 $lang{$id}{'source'}{$tgt} = $english{$id}{'source'}{$tgt};
351 }
352 } elsif ($lp{$tgt} ne 'none' && $lp{$tgt} ne '' && not_ignorelist($id) && !$lang{$id}{'new'}) {
353 $lang{$id}{'notes'} .= "### The <dest> section for '$id:$tgt' is identical to english!\n";
354# print "#!! '$id:$tgt' dest identical ('$lp{$tgt}')\n";
355 }
356 }
357}
358
359# Check voices
360foreach my $id (@langorder) {
361 if (!defined($english{$id})) {
362 next;
363 }
364 my %ep = %{$english{$id}{'voice'}};
365 my %lp;
366
367 if (defined($lang{$id}{'voice'})) {
368 %lp = %{$lang{$id}{'voice'}};
369 } else {
370 %lp = ();
371 }
372
373 foreach my $tgt (keys(%lp)) {
374 if (!defined($ep{$tgt})) {
375 # Delete any targets that have been nuked in master
376 delete($lang{$id}{'voice'}{$tgt});
377 }
378 }
379 foreach my $tgt (keys(%ep)) {
380 if (!defined($lp{$tgt})) {
381 # If it doesn't exist in the language, copy it from English
382 $lang{$id}{'notes'} .= "### The <voice> section for '$id:$tgt' is missing! Copying from english\n";
383# print "#!! '$id:$tgt' voice missing\n";
384 $lang{$id}{'voice'}{$tgt} = $english{$id}{'voice'}{$tgt};
385 } elsif ($lp{$tgt} ne $ep{$tgt}) {
386 if ($lp{$tgt} eq '' && $ep{$tgt} ne '') {
387 # If the lang voice string is blank, complain, and copy from English
388 $lang{$id}{'notes'} .= "### The <voice> section for '$id:$tgt' is blank! Copying from english!\n";
389# print "#!! '$id:$tgt' voice is blank ('$lp{$tgt}' vs '$ep{$tgt}')\n";
390 $lang{$id}{'source'}{$tgt} = $english{$id}{'source'}{$tgt};
391 } elsif ($lp{$tgt} ne '' && $ep{$tgt} eq '') {
392 # If it's not blank, clear it and complain!
393 $lang{$id}{'notes'} .= "### The <voice> section for '$id:$tgt' is not blank!\n";
394 $lang{$id}{'notes'} .= "### the previously used one is commented below:\n";
395 $lang{$id}{'notes'} .= "### $english{$id}{voice}{$tgt}\n";
396# print "#!! '$id:$tgt' voice not blank ('$lp{$tgt}' vs '$ep{$tgt}')\n";
397 $lang{$id}{'source'}{$tgt} = $english{$id}{'source'}{$tgt};
398 }
399 } elsif ($lp{$tgt} ne 'none' && $lp{$tgt} ne '' && not_ignorelist($id) && !$lang{$id}{'new'}) {
400 $lang{$id}{'notes'} .= "### The <voice> section for '$id:$tgt' is identical to english!\n";
401# print "#!! '$id:$tgt' voice identical ('$lp{$tgt}')\n";
402 }
403 }
404}
405
406########## Write new language file
407my $printnotes = 1;
408
409my @tmp = split(/\./, basename($ARGV[0]));
410my $f1 = $tmp[0];
411@tmp = split(/\./, basename($ARGV[1]));
412my $f2 = $tmp[0];
413
414if (index($f2, $f1) > -1) {
415 $printnotes = 0;
416}
417undef $f1;
418undef $f2;
419undef @tmp;
420
421my $fh;
422if ($ARGV[2] ne '-') {
423 open(FH, ">$ARGV[2]") || die ("Can't open $ARGV[2]");
424 $fh = *FH;
425} else {
426 $fh = *STDOUT;
427}
428
429foreach (@langheader) {
430 print $fh $_;
431}
432
433my @finalorder = @langorder; # TODO make configurable vs @englishorder
434foreach my $id (@finalorder) {
435 if (!defined($english{$id})) {
436 next;
437 }
438 my %lp;
439
440 # phrase
441 %lp = %{$lang{$id}{'phrase'}};
442 if (length($lang{$id}{'notes'}) && $printnotes) {
443 print $fh "$lang{$id}{notes}";
444 }
445 print $fh "<phrase>\n";
446 print $fh " id: $lp{id}\n";
447 if ($lp{'desc'} ne '') {
448 print $fh " desc: $lp{desc}\n";
449 } else {
450 print $fh " desc:\n";
451 }
452 print $fh " user: $lp{user}\n";
453
454 # source
455 %lp = combinetgts(%{$lang{$id}{'source'}});
456 print $fh " <source>\n";
457 foreach my $tgt (sort(keys(%lp))) {
458 if ($lp{$tgt} eq 'none') {
459 print $fh " $tgt: $lp{$tgt}\n";
460 } else {
461 print $fh " $tgt: \"$lp{$tgt}\"\n";
462 }
463 }
464 print $fh " </source>\n";
465
466 # dest
467 %lp = combinetgts(%{$lang{$id}{'dest'}});
468 print $fh " <dest>\n";
469 foreach my $tgt (sort(keys(%lp))) {
470 if ($lp{$tgt} eq 'none') {
471 print $fh " $tgt: $lp{$tgt}\n";
472 } else {
473 print $fh " $tgt: \"$lp{$tgt}\"\n";
474 }
475 }
476 print $fh " </dest>\n";
477
478 # voice
479 %lp = combinetgts(%{$lang{$id}{'voice'}});
480 print $fh " <voice>\n";
481 foreach my $tgt (sort(keys(%lp))) {
482 if ($lp{$tgt} eq 'none') {
483 print $fh " $tgt: $lp{$tgt}\n";
484 } else {
485 print $fh " $tgt: \"$lp{$tgt}\"\n";
486 }
487 }
488 print $fh " </voice>\n";
489
490 # FiN
491 print $fh "</phrase>\n";
492}
493
494if ($ARGV[2] ne '-') {
495 close(FH);
496}
diff --git a/tools/voice.pl b/tools/voice.pl
index 216d514065..fefcc49a10 100755
--- a/tools/voice.pl
+++ b/tools/voice.pl
@@ -348,7 +348,7 @@ sub generateclips {
348 if ($existingids) { 348 if ($existingids) {
349 $idfile = $existingids; 349 $idfile = $existingids;
350 } else { 350 } else {
351 $cmd = "genlang -u -e=$english $langfile > $updfile"; 351 $cmd = "updatelang $english $langfile $updfile";
352 print("> $cmd\n") if $verbose; 352 print("> $cmd\n") if $verbose;
353 system($cmd); 353 system($cmd);
354 $cmd = "genlang -o -t=$target -e=$english $updfile 2>/dev/null > $idfile"; 354 $cmd = "genlang -o -t=$target -e=$english $updfile 2>/dev/null > $idfile";