langtool.pl 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. #!/usr/bin/perl
  2. # Processes language_xx.h files into language.cpp and language.h
  3. use strict;
  4. use warnings;
  5. #my @langs = ("en","cz","it","es","de");
  6. my @langs = ("en","cz");
  7. sub parselang
  8. {
  9. my ($filename) = @_;
  10. open(my $fh, '<:encoding(UTF-8)', $filename)
  11. # open(my $fh, '<', $filename)
  12. or die "Could not open file '$filename' $!";
  13. # Create a new hash reference.
  14. my $out = {};
  15. while (my $line = <$fh>) {
  16. chomp $line;
  17. next if (index($line, 'define') == -1 || index($line, 'MSG') == -1);
  18. my $modifiers = {};
  19. my $symbol = '';
  20. my $value = '';
  21. if (index($line, 'define(') == -1) {
  22. # Extended definition, which specifies the string formatting.
  23. $line =~ /(?is)define\s*(\S*)\s*(.*)/;
  24. $symbol = "$1";
  25. $value = $2;
  26. } else {
  27. $line =~ /(?is)define\((.*)\)\s*(\S*)\s*(.*)/;
  28. my $options = $1;
  29. foreach my $key_value (split /,/, $options) {
  30. if ($key_value =~ /\s*(\S*)\s*=\s*(\S*)\s*/) {
  31. ${$modifiers}{$1} = $2;
  32. }
  33. }
  34. $symbol = "$2";
  35. $value = $3;
  36. }
  37. next if (! defined $symbol or length($symbol) == 0);
  38. # Trim whitespaces from both sides
  39. $value =~ s/^\s+|\s+$//g;
  40. #$string =~ s/" MACHINE_NAME "/Prusa i3/;
  41. #$value =~ s/" FIRMWARE_URL "/https:\/\/github.com\/prusa3d\/Prusa-Firmware\//;
  42. #$value =~ s/" PROTOCOL_VERSION "/1.0/;
  43. #$value =~ s/" STRINGIFY\(EXTRUDERS\) "/1/;
  44. #$value =~ s/" MACHINE_UUID "/00000000-0000-0000-0000-000000000000/;
  45. ${$out}{$symbol} = { value=>$value, %$modifiers };
  46. }
  47. return $out;
  48. }
  49. sub pgm_is_whitespace
  50. {
  51. my ($c) = @_;
  52. if (! defined($c)) {
  53. print "pgm_is_whitespace: undefined\n";
  54. exit(1);
  55. }
  56. return $c == ord(' ') || $c == ord('\t') || $c == ord('\r') || $c == ord('\n');
  57. }
  58. sub pgm_is_interpunction
  59. {
  60. my ($c) = @_;
  61. return $c == ord('.') || $c == ord(',') || $c == ord(':') || $c == ord(';') || $c == ord('?') || $c == ord('!') || $c == ord('/');
  62. }
  63. sub break_text_fullscreen
  64. {
  65. my $lines = [];
  66. my ($text_str, $max_linelen) = @_;
  67. if (! defined($text_str) || length($text_str) < 2) {
  68. return $lines;
  69. }
  70. $text_str =~ s/^"//;
  71. $text_str =~ s/([^\\])"/$1/;
  72. $text_str =~ s/\\"/"/;
  73. my @msg = unpack("W*", $text_str);
  74. #my @msg = split("", $text_str);
  75. my $len = $#msg + 1;
  76. my $i = 0;
  77. LINE:
  78. while ($i < $len) {
  79. while ($i < $len && pgm_is_whitespace($msg[$i])) {
  80. $i += 1;
  81. }
  82. if ($i == $len) {
  83. # End of the message.
  84. last LINE;
  85. }
  86. my $msgend2 = $i + (($max_linelen > $len) ? $len : $max_linelen);
  87. my $msgend = $msgend2;
  88. if ($msgend < $len && ! pgm_is_whitespace($msg[$msgend]) && ! pgm_is_interpunction($msg[$msgend])) {
  89. # Splitting a word. Find the start of the current word.
  90. while ($msgend > $i && ! pgm_is_whitespace($msg[$msgend - 1])) {
  91. $msgend -= 1;
  92. }
  93. if ($msgend == $i) {
  94. # Found a single long word, which cannot be split. Just cut it.
  95. $msgend = $msgend2;
  96. }
  97. }
  98. my $outstr = substr($text_str, $i, $msgend - $i);
  99. $i = $msgend;
  100. $outstr =~ s/~/ /g;
  101. #print "Output string: $outstr \n";
  102. push @$lines, $outstr;
  103. }
  104. return $lines;
  105. }
  106. my %texts;
  107. my %attributes;
  108. my $num_languages = 0;
  109. if (1)
  110. {
  111. # First load the common strings.
  112. my $symbols = parselang("language_common.h");
  113. foreach my $key (keys %{$symbols}) {
  114. if (! (exists $texts{$key})) {
  115. my $symbol_value = ${$symbols}{$key};
  116. # Store the symbol value for each language.
  117. $texts{$key} = [ (${$symbol_value}{value}) x ($#langs+1) ];
  118. # Store the possible attributes.
  119. delete ${$symbol_value}{value};
  120. # Store an "is common" attribute.
  121. ${$symbol_value}{common} = 1;
  122. # 4x 80 characters, 4 lines sent over serial line.
  123. ${$symbol_value}{length} = 320;
  124. ${$symbol_value}{lines} = 1;
  125. $attributes{$key} = $symbol_value;
  126. } else {
  127. print "Duplicate key in language_common.h: $key\n";
  128. }
  129. }
  130. }
  131. foreach my $lang (@langs) {
  132. my $symbols = parselang("language_$lang.h");
  133. foreach my $key (keys %{$symbols}) {
  134. if (! (exists $texts{$key})) {
  135. $texts{$key} = [];
  136. }
  137. my $symbol_value = ${$symbols}{$key};
  138. my $strings = $texts{$key};
  139. if (defined $attributes{$key} && defined ${$attributes{$key}}{common} && ${$attributes{$key}}{common} == 1) {
  140. # Common overrides the possible definintions in the language specific files.
  141. } else {
  142. die "Symbol $key defined first in $lang, undefined in the preceding language files."
  143. if (scalar(@$strings) != $num_languages);
  144. push @$strings, ${$symbol_value}{value};
  145. if ($lang eq 'en') {
  146. # The english texts may contain attributes. Store them into %attributes.
  147. delete ${$symbol_value}{value};
  148. $attributes{$key} = $symbol_value;
  149. }
  150. }
  151. }
  152. $num_languages += 1;
  153. foreach my $key (keys %texts) {
  154. my $strings = $texts{$key};
  155. if (scalar(@$strings) < $num_languages) {
  156. # die "Symbol $key undefined in $lang."
  157. print "Symbol $key undefined in language \"$lang\". Using the english variant:\n";
  158. print "\t", ${$strings}[0], "\n";
  159. push @$strings, ${$strings}[0];
  160. }
  161. }
  162. }
  163. my $filename = 'language_all.h';
  164. open(my $fh, '>', $filename) or die "Could not open file '$filename' $!";
  165. # For the programmatic access to the program memory, read
  166. # http://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html
  167. print $fh <<END
  168. #ifndef LANGUAGE_ALL_H
  169. #define LANGUAGE_ALL_H
  170. #include <avr/pgmspace.h>
  171. // Language indices into their particular symbol tables.
  172. END
  173. ;
  174. # Export symbolic IDs of languages.
  175. for my $i (0 .. $#langs) {
  176. my $lang = uc $langs[$i];
  177. print $fh "#define LANG_ID_$lang $i\n";
  178. }
  179. print $fh <<END
  180. // Language is not defined and it shall be selected from the menu.
  181. #define LANG_ID_FORCE_SELECTION 254
  182. // Language is not defined on a virgin RAMBo board.
  183. #define LANG_ID_UNDEFINED 255
  184. // Default language ID, if no language is selected.
  185. #define LANG_ID_DEFAULT LANG_ID_CZ
  186. // Number of languages available in the language table.
  187. #define LANG_NUM ${num_languages}
  188. // Currectly active language selection.
  189. extern unsigned char lang_selected;
  190. #define LANG_TABLE_SELECT_EXPLICIT(TABLE, LANG) ((const char*)(pgm_read_ptr(TABLE + (LANG))))
  191. #define LANG_TABLE_SELECT(TABLE) LANG_TABLE_SELECT_EXPLICIT(TABLE, lang_selected)
  192. END
  193. ;
  194. foreach my $key (sort(keys %texts)) {
  195. my $strings = $texts{$key};
  196. if (@{$strings} == grep { $_ eq ${$strings}[0] } @{$strings}) {
  197. # All strings are English.
  198. print $fh "extern const char* const ${key}_LANG_TABLE[1];\n";
  199. print $fh "#define $key LANG_TABLE_SELECT_EXPLICIT(${key}_LANG_TABLE, 0)\n";
  200. } else {
  201. print $fh "extern const char* const ${key}_LANG_TABLE[LANG_NUM];\n";
  202. print $fh "#define $key LANG_TABLE_SELECT(${key}_LANG_TABLE)\n";
  203. print $fh "#define ${key}_EXPLICIT(LANG) LANG_TABLE_SELECT_EXPLICIT(${key}_LANG_TABLE, LANG)\n"
  204. if ($key eq "MSG_LANGUAGE_NAME" || $key eq "MSG_LANGUAGE_SELECT");
  205. }
  206. }
  207. print $fh <<END
  208. extern char* CAT2(const char *s1,const char *s2);
  209. extern char* CAT4(const char *s1,const char *s2,const char *s3,const char *s4);
  210. #endif //LANGUAGE_ALL.H
  211. END
  212. ;
  213. close $fh;
  214. print ".h created\n";
  215. $filename = 'language_all.cpp';
  216. open($fh, '>', $filename) or die "Could not open file '$filename' $!";
  217. print $fh <<'END'
  218. #include "Configuration_prusa.h"
  219. #include "language_all.h"
  220. #define LCD_WIDTH 20
  221. extern unsigned char lang_selected;
  222. END
  223. ;
  224. my @keys = sort(keys %texts);
  225. foreach my $key (@keys) {
  226. my $strings = $texts{$key};
  227. if (@{$strings} == grep { $_ eq ${$strings}[0] } @{$strings}) {
  228. # Shrink the array to a single value.
  229. $strings = [${$strings}[0]];
  230. }
  231. for (my $i = 0; $i <= $#{$strings}; $i ++) {
  232. my $suffix = uc($langs[$i]);
  233. if ($i == 0 || ${$strings}[$i] ne ${$strings}[0]) {
  234. print $fh "const char ${key}_${suffix}[] PROGMEM = ${$strings}[$i];\n";
  235. }
  236. }
  237. my $langnum = $#{$strings}+1;
  238. if ($langnum == $#langs+1) {
  239. $langnum = "LANG_NUM";
  240. }
  241. print $fh "const char * const ${key}_LANG_TABLE[$langnum] PROGMEM = {\n";
  242. for (my $i = 0; $i <= $#{$strings}; $i ++) {
  243. my $suffix = uc($langs[$i]);
  244. if ($i == 0 || ${$strings}[$i] ne ${$strings}[0]) {
  245. print $fh "\t${key}_${suffix}";
  246. } else {
  247. print $fh "\t${key}_EN";
  248. }
  249. print $fh ',' if $i < $#{$strings};
  250. print $fh "\n";
  251. }
  252. print $fh "};\n\n";
  253. }
  254. print $fh <<'END'
  255. char langbuffer[LCD_WIDTH+1];
  256. char* CAT2(const char *s1,const char *s2) {
  257. unsigned char len=0;
  258. strncpy_P(langbuffer+len,s1,LCD_WIDTH-len);
  259. len+=strlen_P(s1);
  260. strncpy_P(langbuffer+len,s2,LCD_WIDTH-len);
  261. return langbuffer;
  262. }
  263. char* CAT4(const char *s1,const char *s2,const char *s3,const char *s4) {
  264. unsigned char len=0;
  265. strncpy_P(langbuffer+len,s1,LCD_WIDTH-len);
  266. len+=strlen_P(s1);
  267. strncpy_P(langbuffer+len,s2,LCD_WIDTH-len);
  268. len+=strlen_P(s2);
  269. strncpy_P(langbuffer+len,s3,LCD_WIDTH-len);
  270. len+=strlen_P(s3);
  271. strncpy_P(langbuffer+len,s4,LCD_WIDTH-len);
  272. return langbuffer;
  273. }
  274. END
  275. ;
  276. print ".cpp created.\nDone!\n";
  277. my $verify_only = 1;
  278. for my $lang (0 .. $#langs) {
  279. print "Language: $langs[$lang]\n";
  280. foreach my $key (@keys) {
  281. my $strings = $texts{$key};
  282. my %attrib = %{$attributes{$key}};
  283. my $message = ${$strings}[$lang];
  284. $message =~ /\S*"(.*)"\S*/;
  285. $message = $1;
  286. if ($lang == 0 || ${$strings}[0] ne $message) {
  287. # If the language is not English, don't show the non-translated message.
  288. my $max_nlines = $attrib{lines} // 1;
  289. my $max_linelen = $attrib{length} // (($max_nlines > 1) ? 20 : 17);
  290. # if (! $verify_only) {
  291. # if ($nlines > 1) {
  292. # print "Multi-line message: $message. Breaking to $nlines lines:\n";
  293. # print "\t$_\n" foreach (@{$lines});
  294. # }
  295. # }
  296. if ($max_nlines <= 1) {
  297. my $linelen = length($message);
  298. if ($linelen > $max_linelen) {
  299. print "Key $key, language $langs[$lang], line length: $linelen, max length: $max_linelen\n";
  300. print "\t$message\n";
  301. }
  302. } else {
  303. my $lines = break_text_fullscreen($message, $max_linelen);
  304. my $nlines = @{$lines};
  305. if ($nlines > $max_nlines) {
  306. print "Key $key, language $langs[$lang], lines: $nlines, max lines: $max_nlines\n";
  307. print "\t$_\n" foreach (@{$lines});
  308. }
  309. }
  310. }
  311. }
  312. }