#!/usr/bin/perl # Processes language_xx.h files into language.cpp and language.h use strict; use warnings; my @langs = ("en","cz","it","es","de"); sub parselang { my ($filename) = @_; open(my $fh, '<:encoding(UTF-8)', $filename) # open(my $fh, '<', $filename) or die "Could not open file '$filename' $!"; # Create a new hash reference. my $out = {}; while (my $line = <$fh>) { chomp $line; next if (index($line, 'define') == -1 || index($line, 'MSG') == -1); my $modifiers = {}; my $symbol = ''; my $value = ''; if (index($line, 'define(') == -1) { # Extended definition, which specifies the string formatting. $line =~ /(?is)define\s*(\S*)\s*(.*)/; $symbol = "$1"; $value = $2; } else { $line =~ /(?is)define\((.*)\)\s*(\S*)\s*(.*)/; my $options = $1; foreach my $key_value (split /,/, $options) { if ($key_value =~ /\s*(\S*)\s*=\s*(\S*)\s*/) { ${$modifiers}{$1} = $2; } } $symbol = "$2"; $value = $3; } next if (! defined $symbol or length($symbol) == 0); # Trim whitespaces from both sides $value =~ s/^\s+|\s+$//g; #$string =~ s/" MACHINE_NAME "/Prusa i3/; $value =~ s/" FIRMWARE_URL "/https:\/\/github.com\/prusa3d\/Prusa-i3-Plus\//; $value =~ s/" PROTOCOL_VERSION "/1.0/; $value =~ s/" STRINGIFY\(EXTRUDERS\) "/1/; $value =~ s/" MACHINE_UUID "/00000000-0000-0000-0000-000000000000/; ${$out}{$symbol} = { value=>$value, %$modifiers }; } return $out; } sub pgm_is_whitespace { my ($c) = @_; if (! defined($c)) { print "pgm_is_whitespace: undefined\n"; exit(1); } return $c == ord(' ') || $c == ord('\t') || $c == ord('\r') || $c == ord('\n'); } sub pgm_is_interpunction { my ($c) = @_; return $c == ord('.') || $c == ord(',') || $c == ord(':') || $c == ord(';') || $c == ord('?') || $c == ord('!') || $c == ord('/'); } sub break_text_fullscreen { my $lines = []; my ($text_str, $max_linelen) = @_; if (! defined($text_str) || length($text_str) < 2) { return $lines; } $text_str =~ s/^"//; $text_str =~ s/([^\\])"/$1/; $text_str =~ s/\\"/"/; my @msg = unpack("W*", $text_str); #my @msg = split("", $text_str); my $len = $#msg + 1; my $i = 0; LINE: while ($i < $len) { while ($i < $len && pgm_is_whitespace($msg[$i])) { $i += 1; } if ($i == $len) { # End of the message. last LINE; } my $msgend2 = $i + (($max_linelen > $len) ? $len : $max_linelen); my $msgend = $msgend2; if ($msgend < $len && ! pgm_is_whitespace($msg[$msgend]) && ! pgm_is_interpunction($msg[$msgend])) { # Splitting a word. Find the start of the current word. while ($msgend > $i && ! pgm_is_whitespace($msg[$msgend - 1])) { $msgend -= 1; } if ($msgend == $i) { # Found a single long word, which cannot be split. Just cut it. $msgend = $msgend2; } } my $outstr = substr($text_str, $i, $msgend - $i); $i = $msgend; $outstr =~ s/~/ /g; #print "Output string: $outstr \n"; push @$lines, $outstr; } return $lines; } my %texts; my %attributes; my $num_languages = 0; if (1) { # First load the common strings. my $symbols = parselang("language_common.h"); foreach my $key (keys %{$symbols}) { if (! (exists $texts{$key})) { my $symbol_value = ${$symbols}{$key}; # Store the symbol value for each language. $texts{$key} = [ (${$symbol_value}{value}) x ($#langs+1) ]; # Store the possible attributes. delete ${$symbol_value}{value}; # Store an "is common" attribute. ${$symbol_value}{common} = 1; # 4x 80 characters, 4 lines sent over serial line. ${$symbol_value}{length} = 320; ${$symbol_value}{lines} = 1; $attributes{$key} = $symbol_value; } else { print "Duplicate key in language_common.h: $key\n"; } } } foreach my $lang (@langs) { my $symbols = parselang("language_$lang.h"); foreach my $key (keys %{$symbols}) { if (! (exists $texts{$key})) { $texts{$key} = []; } my $symbol_value = ${$symbols}{$key}; my $strings = $texts{$key}; if (defined $attributes{$key} && defined ${$attributes{$key}}{common} && ${$attributes{$key}}{common} == 1) { # Common overrides the possible definintions in the language specific files. } else { die "Symbol $key defined first in $lang, undefined in the preceding language files." if (scalar(@$strings) != $num_languages); push @$strings, ${$symbol_value}{value}; if ($lang eq 'en') { # The english texts may contain attributes. Store them into %attributes. delete ${$symbol_value}{value}; $attributes{$key} = $symbol_value; } } } $num_languages += 1; foreach my $key (keys %texts) { my $strings = $texts{$key}; if (scalar(@$strings) < $num_languages) { # die "Symbol $key undefined in $lang." print "Symbol $key undefined in language \"$lang\". Using the english variant:\n"; print "\t", ${$strings}[0], "\n"; push @$strings, ${$strings}[0]; } } } my $filename = 'language_all.h'; open(my $fh, '>', $filename) or die "Could not open file '$filename' $!"; # For the programmatic access to the program memory, read # http://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html print $fh < // Language indices into their particular symbol tables. END ; # Export symbolic IDs of languages. for my $i (0 .. $#langs) { my $lang = uc $langs[$i]; print $fh "#define LANG_ID_$lang $i\n"; } print $fh <', $filename) or die "Could not open file '$filename' $!"; print $fh <<'END' #include "Configuration_prusa.h" #include "language_all.h" #define LCD_WIDTH 20 extern unsigned char lang_selected; END ; my @keys = sort(keys %texts); foreach my $key (@keys) { my $strings = $texts{$key}; if (@{$strings} == grep { $_ eq ${$strings}[0] } @{$strings}) { # Shrink the array to a single value. $strings = [${$strings}[0]]; } for (my $i = 0; $i <= $#{$strings}; $i ++) { my $suffix = uc($langs[$i]); if ($i == 0 || ${$strings}[$i] ne ${$strings}[0]) { print $fh "const char ${key}_${suffix}[] PROGMEM = ${$strings}[$i];\n"; } } my $langnum = $#{$strings}+1; if ($langnum == $#langs+1) { $langnum = "LANG_NUM"; } print $fh "const char * const ${key}_LANG_TABLE[$langnum] PROGMEM = {\n"; for (my $i = 0; $i <= $#{$strings}; $i ++) { my $suffix = uc($langs[$i]); if ($i == 0 || ${$strings}[$i] ne ${$strings}[0]) { print $fh "\t${key}_${suffix}"; } else { print $fh "\t${key}_EN"; } print $fh ',' if $i < $#{$strings}; print $fh "\n"; } print $fh "};\n\n"; } print $fh <<'END' char langbuffer[LCD_WIDTH+1]; char* CAT2(const char *s1,const char *s2) { unsigned char len=0; strncpy_P(langbuffer+len,s1,LCD_WIDTH-len); len+=strlen_P(s1); strncpy_P(langbuffer+len,s2,LCD_WIDTH-len); return langbuffer; } char* CAT4(const char *s1,const char *s2,const char *s3,const char *s4) { unsigned char len=0; strncpy_P(langbuffer+len,s1,LCD_WIDTH-len); len+=strlen_P(s1); strncpy_P(langbuffer+len,s2,LCD_WIDTH-len); len+=strlen_P(s2); strncpy_P(langbuffer+len,s3,LCD_WIDTH-len); len+=strlen_P(s3); strncpy_P(langbuffer+len,s4,LCD_WIDTH-len); return langbuffer; } END ; print ".cpp created.\nDone!\n"; my $verify_only = 1; for my $lang (0 .. $#langs) { print "Language: $langs[$lang]\n"; foreach my $key (@keys) { my $strings = $texts{$key}; my %attrib = %{$attributes{$key}}; my $message = ${$strings}[$lang]; $message =~ /\S*"(.*)"\S*/; $message = $1; if ($lang == 0 || ${$strings}[0] ne $message) { # If the language is not English, don't show the non-translated message. my $max_nlines = $attrib{lines} // 1; my $max_linelen = $attrib{length} // (($max_nlines > 1) ? 20 : 17); # if (! $verify_only) { # if ($nlines > 1) { # print "Multi-line message: $message. Breaking to $nlines lines:\n"; # print "\t$_\n" foreach (@{$lines}); # } # } if ($max_nlines <= 1) { my $linelen = length($message); if ($linelen > $max_linelen) { print "Key $key, language $langs[$lang], line length: $linelen, max length: $max_linelen\n"; print "\t$message\n"; } } else { my $lines = break_text_fullscreen($message, $max_linelen); my $nlines = @{$lines}; if ($nlines > $max_nlines) { print "Key $key, language $langs[$lang], lines: $nlines, max lines: $max_nlines\n"; print "\t$_\n" foreach (@{$lines}); } } } } }