manage_i18n.pl.in 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. #! @PERL@ -w
  2. #+##############################################################################
  3. #
  4. # manage_i18n.pl: manage translation files
  5. #
  6. # Copyright (C) 2003, 2005, 2009, 2010 Free Software Foundation, Inc.
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 3 of the License,
  11. # or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. # Originally written by Patrice Dumas.
  22. #
  23. #-##############################################################################
  24. # for file in ../i18n/*; do echo $file | grep -qs 'old\|CVS' && continue; bfile=`basename $file .thl`; post='.us-ascii.po'; msgen texi2html_document.pot > $bfile$post-1; sed -i -e 's/CHARSET/us-ascii/' $bfile$post-1 ; msgfilter --keep-header -i $bfile$post-1 -o $bfile$post ../manage_i18n.pl filter -I ../ --lang $bfile; done
  25. # for file in ../i18n/hu.thl ../i18n/it.thl ; do echo $file | grep -qs 'old\|CVS' && continue; bfile=`basename $file .thl`; post='.po'; msgen texi2html_document.pot > $bfile$post-1; sed -i -e 's/CHARSET/utf-8/' $bfile$post-1 ; msgfilter --keep-header -i $bfile$post-1 -o $bfile$post ../manage_i18n.pl filter -I ../ --lang $bfile; done
  26. # remove every translations while keeping the header
  27. # msgfilter --keep-header -i es.us-ascii.po 'true' > es.us-ascii.po-head
  28. # duplicate english messages to msgstr
  29. # msgen es.us-ascii.po-head > es.us-ascii.po-en
  30. # use the new translations
  31. # msgfilter --keep-header -i es.us-ascii.po-en ../manage_i18n.pl filter -I ../ --lang es > es.us-ascii.po-new
  32. #
  33. # mv es.us-ascii.po-new es.us-ascii.po
  34. # This requires perl version 5 or higher
  35. require 5.0;
  36. use strict;
  37. use File::Copy;
  38. #use Data::Dumper;
  39. @REQUIRE_DATA_DUMPER@;
  40. use Getopt::Long qw(GetOptions);
  41. my $USE_DATA_DUMPER = '@USE_DATA_DUMPER@';
  42. select(STDERR);
  43. $| = 1;
  44. select(STDOUT);
  45. $| = 1;
  46. my $help = 0;
  47. my $language;
  48. my $dir = 'i18n'; # name of the directory containing the per language files
  49. my $output; # file containing all the translations or output directory
  50. # = 'translations.pl'; # file containing all the translations
  51. my @known_languages = ('en', 'de', 'nl', 'es', 'no', 'pt', 'pt_BR', 'ja',
  52. 'fr', 'it', 'hu'); # The supported languages
  53. my @searched_dirs = (); # dir searched for sources and language files
  54. #my $template = 'template';
  55. my $template_lang = 'en';
  56. my @sources = ('texi2html.pl', 'texi2html.pm', 'T2h_i18n.pm',
  57. 'examples/roff.pm', 'examples/noheaders.pm');
  58. my $lang;
  59. my $charset;
  60. GetOptions('Include=s' => \@searched_dirs, 'output=s' => \$output,
  61. 'directory=s' => \$dir, 'help+' => \$help, 'lang=s' => \$lang,
  62. 'charset=s' => \$charset);
  63. if ($help)
  64. {
  65. my $command_basename = $0;
  66. $command_basename =~ s%.*/%%;
  67. my $usage_text = <<EOT;
  68. Usage:
  69. $command_basename -h
  70. $command_basename [-I <dir>][-d <i18n_dir>][-o <out_dir>] template source_files...
  71. $command_basename [-I <dir>][-d <i18n_dir>][-o <out_dir>] update [ language... ]
  72. $command_basename [-I <dir>][-d <i18n_dir>][-o <result_file>] merge
  73. Default languages are files in the i18n_dir with .thl extension.
  74. EOT
  75. print $usage_text;
  76. exit 0;
  77. }
  78. if (!@searched_dirs)
  79. {
  80. @searched_dirs = ('.');
  81. }
  82. if (@ARGV < 1)
  83. {
  84. die "Need a command\n";
  85. }
  86. my $command = shift @ARGV;
  87. my $output_dir = $dir;
  88. $output_dir = $output if (defined($output));
  89. my $translations_file = 'translations.pl';
  90. $translations_file = $output if (defined($output));
  91. sub locate_file($$)
  92. {
  93. my $file = shift;
  94. my $directories = shift;
  95. $directories = \@searched_dirs if !defined($directories);
  96. if ($file =~ /^\//)
  97. {
  98. return $file if (-e $file and -r $file);
  99. }
  100. else
  101. {
  102. foreach my $dir (@$directories)
  103. {
  104. next unless (-d "$dir");
  105. return "$dir/$file" if (-e "$dir/$file" and -r "$dir/$file");
  106. }
  107. }
  108. return undef;
  109. }
  110. my $defaults_file = "$dir/$template_lang";
  111. use vars qw(
  112. $LANGUAGES
  113. $T2H_OBSOLETE_STRINGS
  114. );
  115. # Strings not in code
  116. my $template_strings =
  117. {
  118. 'January' => '',
  119. 'February' => '',
  120. 'March' => '',
  121. 'April' => '',
  122. 'May' => '',
  123. 'June' => '',
  124. 'July' => '',
  125. 'August' => '',
  126. 'September' => '',
  127. 'October' => '',
  128. 'November' => '',
  129. 'December' => '',
  130. 'T2H_today' => '%s, %d %d',
  131. };
  132. # Handle per language files
  133. if ($USE_DATA_DUMPER)
  134. {
  135. $Data::Dumper::Sortkeys = 1;
  136. }
  137. sub update_language_file($$$$$);
  138. #die "No suitable $dir directory\n" unless (-d $dir and -r $dir);
  139. sub get_languages($$)
  140. {
  141. my $directories = shift;
  142. my $i18n_dir = shift;
  143. my @languages = ();
  144. foreach my $searched_dir (@$directories)
  145. {
  146. my $dir = "$searched_dir/$i18n_dir";
  147. if (opendir DIR, $dir)
  148. {
  149. my @new_languages = grep {
  150. ! /^\./ &&
  151. ! /\.(bak|orig|old|dpkg-old|rpmnew|rpmsave)$/ &&
  152. ! /~$/ &&
  153. ! /^#.*#$/ &&
  154. /\.thl$/ &&
  155. -f $dir . '/' . $_
  156. } readdir DIR;
  157. closedir DIR;
  158. foreach my $language (@new_languages)
  159. {
  160. $language =~ s/\.thl$//;
  161. push @languages, $language unless grep {$_ eq $language}
  162. @languages;
  163. }
  164. }
  165. }
  166. my @known = @known_languages;
  167. foreach my $lang (@languages)
  168. {
  169. if (grep {$_ eq $lang} @known)
  170. {
  171. @known = grep {$_ ne $lang} @known;
  172. }
  173. else
  174. {
  175. warn "Remark: you could update the known languages array for `$lang'\n";
  176. }
  177. }
  178. warn "Remark: the following known languages have no corresponding file: @known\n" if (@known);
  179. return @languages;
  180. }
  181. sub merge_i18n_files($$$)
  182. {
  183. my $directories = shift;
  184. my $i18n_dir = shift;
  185. my $translation_file = shift;
  186. my @languages = get_languages($directories, $i18n_dir);
  187. die "No languages found\n" unless (@languages);
  188. if (-f $translation_file)
  189. {
  190. unless (File::Copy::copy ($translation_file, "$translation_file.old"))
  191. {
  192. die "Error copying $translation_file to $translation_file.old\n";
  193. }
  194. }
  195. open (TRANSLATIONS, ">$translation_file")
  196. || die "open(>$translation_file) failed: $!";
  197. foreach my $lang (@languages)
  198. {
  199. my $file = locate_file("$i18n_dir/$lang.thl", $directories);
  200. next unless defined($file);
  201. unless (open (FILE, $file))
  202. {
  203. warn "open $file failed: $!\n";
  204. return;
  205. }
  206. while (<FILE>)
  207. {
  208. print TRANSLATIONS;
  209. }
  210. close FILE;
  211. }
  212. print TRANSLATIONS "\n1;\n"; # ensure end with true value
  213. close (TRANSLATIONS) || warn "close(>$translation_file) failed: $!";
  214. }
  215. sub get_language_hashes($$)
  216. {
  217. my $from_file = shift;
  218. my $lang = shift;
  219. if (defined($from_file) and -f $from_file)
  220. {
  221. eval { require($from_file) ;};
  222. if ($@)
  223. {
  224. warn "require $from_file failed: $@\n";
  225. return (undef,undef);
  226. }
  227. if (!defined($LANGUAGES->{$lang}))
  228. {
  229. warn "LANGUAGES->{$lang} not defined in $from_file\n";
  230. return (undef,undef);
  231. }
  232. }
  233. return ($LANGUAGES->{$lang}, $T2H_OBSOLETE_STRINGS->{$lang})
  234. }
  235. sub get_translation_string($$$$)
  236. {
  237. my $directories = shift;
  238. my $i18n_dir = shift;
  239. my $lang = shift;
  240. my $string_to_find = shift;
  241. my $from_file = locate_file("$i18n_dir/$lang.thl", $directories);
  242. my ($strings, $obsolete_strings) = get_language_hashes($from_file, $lang);
  243. return $strings->{$string_to_find} if (defined($strings) and defined($strings->{$string_to_find}));
  244. return $obsolete_strings->{$string_to_find} if (defined($obsolete_strings) and defined($obsolete_strings->{$string_to_find}));
  245. return undef;
  246. }
  247. sub update_language_hash($$$)
  248. {
  249. my $from_file = shift;
  250. my $lang = shift;
  251. my $reference = shift;
  252. if (defined($from_file) and -f $from_file)
  253. {
  254. eval { require($from_file) ;};
  255. if ($@)
  256. {
  257. warn "require $from_file failed: $@\n";
  258. return;
  259. }
  260. if (!defined($LANGUAGES->{$lang}))
  261. {
  262. warn "LANGUAGES->{$lang} not defined in $from_file\n";
  263. return;
  264. }
  265. }
  266. if (!defined($T2H_OBSOLETE_STRINGS->{$lang}))
  267. {
  268. $T2H_OBSOLETE_STRINGS->{$lang} = {};
  269. }
  270. if (!defined($LANGUAGES->{$lang}))
  271. {
  272. $LANGUAGES->{$lang} = {};
  273. }
  274. foreach my $string (keys %{$LANGUAGES->{$lang}})
  275. {
  276. $T2H_OBSOLETE_STRINGS->{$lang}->{$string} = $LANGUAGES->{$lang}->{$string}
  277. if (defined($LANGUAGES->{$lang}->{$string}) and ($LANGUAGES->{$lang}->{$string} ne ''));
  278. }
  279. $LANGUAGES->{$lang} = {};
  280. foreach my $string (keys (%{$reference}))
  281. {
  282. if (exists($T2H_OBSOLETE_STRINGS->{$lang}->{$string}) and
  283. defined($T2H_OBSOLETE_STRINGS->{$lang}->{$string}) and
  284. ($T2H_OBSOLETE_STRINGS->{$lang}->{$string} ne ''))
  285. {
  286. $LANGUAGES->{$lang}->{$string} = $T2H_OBSOLETE_STRINGS->{$lang}->{$string};
  287. delete $T2H_OBSOLETE_STRINGS->{$lang}->{$string};
  288. }
  289. else
  290. {
  291. $LANGUAGES->{$lang}->{$string} = '';
  292. }
  293. }
  294. return 1;
  295. }
  296. # Based on the template file, update the different language files
  297. sub update_i18n_files($$$$@)
  298. {
  299. my $directories = shift;
  300. my $i18n_dir = shift;
  301. my $out_i18n_dir = shift;
  302. my $template_language = shift;
  303. my @languages = grep {$template_language ne $_}
  304. get_languages($directories, $i18n_dir);
  305. if (@_)
  306. {
  307. @languages = ();
  308. foreach my $lang (@_)
  309. {
  310. unless (grep {$lang eq $_} @known_languages)
  311. {
  312. warn "Remark: you could update the known languages array for `$lang'\n";
  313. }
  314. push (@languages, $lang) unless (grep {$lang eq $_} @languages);
  315. }
  316. }
  317. unless (@languages)
  318. {
  319. warn "No languages to update\n" ;
  320. return;
  321. }
  322. my $template_file = locate_file("$i18n_dir/$template_language.thl",
  323. $directories);
  324. die "No $i18n_dir/$template_language.thl found\n" unless
  325. (defined($template_file));
  326. eval { require($template_file) ;};
  327. if ($@)
  328. {
  329. die "require $template_file failed: $@\n";
  330. }
  331. die "LANGUAGE->{'$template_language'} undef after require $template_file\n" unless
  332. (defined($LANGUAGES) and defined($LANGUAGES->{$template_language}));
  333. foreach my $string (keys(%$template_strings))
  334. {
  335. die "template string $string undef"
  336. unless (defined($LANGUAGES->{$template_language}->{$string}));
  337. }
  338. unless (-d $out_i18n_dir)
  339. {
  340. if (!mkdir($out_i18n_dir, oct(755)))
  341. {
  342. die "cannot mkdir $out_i18n_dir: $!\n";
  343. }
  344. }
  345. foreach my $lang (@languages)
  346. {
  347. update_language_file($directories, $i18n_dir, $out_i18n_dir,
  348. $template_language, $lang);
  349. }
  350. return 1;
  351. }
  352. sub copy_or_touch($$)
  353. {
  354. my $from_file = shift;
  355. my $to_file = shift;
  356. my $atime = time;
  357. my $mtime = $atime;
  358. if ($to_file eq $from_file)
  359. {
  360. utime $atime, $mtime, $from_file;
  361. return;
  362. }
  363. elsif (-f $to_file)
  364. {
  365. my $identical_files = 1;
  366. if (open(FROMFILE, $from_file))
  367. {
  368. if (open(TOFILE, $to_file))
  369. {
  370. my $to_file_lines = '';
  371. my $from_file_lines = '';
  372. while (<FROMFILE>)
  373. {
  374. $from_file_lines .= $_;
  375. }
  376. while (<TOFILE>)
  377. {
  378. $to_file_lines .= $_;
  379. }
  380. if ($from_file_lines eq $to_file_lines)
  381. {
  382. utime $atime, $mtime, $from_file;
  383. return;
  384. }
  385. }
  386. }
  387. else
  388. {
  389. warn "Error opening $from_file\n";
  390. utime $atime, $mtime, $to_file;
  391. }
  392. }
  393. unless (File::Copy::copy($from_file, $to_file))
  394. {
  395. warn "Error copying $from_file to $to_file\n";
  396. }
  397. }
  398. sub update_language_file($$$$$)
  399. {
  400. my $directories = shift;
  401. my $i18n_dir = shift;
  402. my $out_i18n_dir = shift;
  403. my $template_language = shift;
  404. my $lang = shift;
  405. my $from_file = locate_file("$i18n_dir/$lang.thl", $directories);
  406. my $to_file = "$out_i18n_dir/$lang.thl";
  407. return unless (defined($from_file));
  408. return unless (update_language_hash($from_file, $lang,
  409. $LANGUAGES->{$template_language}));
  410. if (-f $to_file)
  411. {
  412. unless (File::Copy::copy($to_file, "$to_file.old"))
  413. {
  414. warn "Error copying $to_file to $to_file.old\n";
  415. return;
  416. }
  417. }
  418. if (!$USE_DATA_DUMPER)
  419. {
  420. copy_or_touch($from_file, $to_file);
  421. return;
  422. }
  423. unless (open(FILE, ">$to_file"))
  424. {
  425. warn "open $to_file failed: $!\n";
  426. return;
  427. }
  428. print FILE "" . Data::Dumper->Dump([$LANGUAGES->{$lang}], [ "LANGUAGES->{'$lang'}" ]);
  429. print FILE "\n";
  430. print FILE Data::Dumper->Dump([$T2H_OBSOLETE_STRINGS->{$lang}], [ "T2H_OBSOLETE_STRINGS->{'$lang'}"]);
  431. print FILE "\n";
  432. print FILE "\n";
  433. close FILE;
  434. }
  435. sub update_template($$$$@)
  436. {
  437. my $directories = shift;
  438. my $i18n_dir = shift;
  439. my $template_language = shift;
  440. my $out_dir = shift;
  441. my $source_strings = {};
  442. unless (@_)
  443. {
  444. die "No source files\n";
  445. }
  446. foreach my $source (@_)
  447. {
  448. my $source_file = locate_file($source, $directories);
  449. unless (defined($source_file))
  450. {
  451. warn "$source_file not found\n";
  452. next;
  453. }
  454. unless (open (FILE, "$source_file"))
  455. {
  456. warn "open $source_file failed: $!\n";
  457. next;
  458. }
  459. my $line_nr = 0;
  460. while (<FILE>)
  461. {
  462. $line_nr++;
  463. my $string;
  464. next if /^\s*#/;
  465. while ($_)
  466. {
  467. if (defined($string))
  468. {
  469. if (s/^([^\\']*)(\\|')//)
  470. {
  471. $string .= $1 if (defined($1));
  472. if ($2 eq "'")
  473. {
  474. $source_strings->{$string} = '' ;
  475. $string = undef;
  476. }
  477. else
  478. {
  479. if (s/^(.)//)
  480. {
  481. #$string .= '\\' . $1;
  482. $string .= $1;
  483. }
  484. else
  485. {
  486. warn "\\ at end of line, file $source_file, line nr $line_nr\n";
  487. $source_strings->{$string} = '' ;
  488. $string = undef;
  489. }
  490. }
  491. }
  492. else
  493. {
  494. warn "string not closed file $source_file, line nr $line_nr\n";
  495. $source_strings->{$string} = '' ;
  496. $string = undef;
  497. }
  498. }
  499. elsif (s/^.*?&\$I\s*\('// or s/^.*? gdt\('//)
  500. #elsif (s/^.*?&\$I\s*\('//)
  501. {
  502. $string = '';
  503. }
  504. else
  505. {
  506. last;
  507. }
  508. }
  509. }
  510. close FILE;
  511. }
  512. foreach my $string (keys(%$template_strings))
  513. {
  514. $source_strings->{$string} = $template_strings->{$string};
  515. }
  516. my $template_file =
  517. locate_file("$i18n_dir/$template_language.thl",$directories);
  518. die unless (update_language_hash($template_file,
  519. $template_language, $source_strings));
  520. foreach my $string (keys(%$template_strings))
  521. { # use values in template_srings if it exists
  522. my $existing_string = $LANGUAGES->{$template_language}->{$string};
  523. $LANGUAGES->{$template_language}->{$string} = $template_strings->{$string}
  524. if ((!defined($existing_string)) or ($existing_string eq ''));
  525. }
  526. unless (-d $out_dir)
  527. {
  528. if (!mkdir($out_dir, oct(755)))
  529. {
  530. die "cannot mkdir $out_dir: $!\n";
  531. }
  532. }
  533. my $out_template_file = "$out_dir/$template_language";
  534. if (!$USE_DATA_DUMPER)
  535. {
  536. return if (!defined($template_file));
  537. copy_or_touch($template_file, $out_template_file);
  538. return;
  539. }
  540. unless (open(TEMPLATE, ">$out_template_file"))
  541. {
  542. die "open $out_template_file failed: $!\n";
  543. }
  544. print TEMPLATE "" . Data::Dumper->Dump([$LANGUAGES->{$template_language}], [ "LANGUAGES->{'$template_language'}" ]);
  545. print TEMPLATE "\n";
  546. print TEMPLATE Data::Dumper->Dump([$T2H_OBSOLETE_STRINGS->{$template_language}], [ "T2H_OBSOLETE_STRINGS->{'$template_language'}"]);
  547. print TEMPLATE "\n";
  548. print TEMPLATE "\n";
  549. close TEMPLATE;
  550. }
  551. if ($command eq 'update')
  552. {
  553. update_i18n_files(\@searched_dirs, $dir, $output_dir,
  554. $template_lang, @ARGV);
  555. }
  556. elsif ($command eq 'merge')
  557. {
  558. merge_i18n_files(\@searched_dirs, $dir, $translations_file);
  559. }
  560. elsif ($command eq 'template')
  561. {
  562. my @source_files = @ARGV;
  563. unless (@source_files)
  564. {
  565. @source_files = @sources;
  566. }
  567. update_template(\@searched_dirs, $dir, $template_lang, $output_dir,
  568. @source_files);
  569. }
  570. #elsif ($command eq 'all')
  571. #{
  572. # my @source_files = @ARGV;
  573. # unless (@source_files)
  574. # {
  575. # @source_files = @sources;
  576. # }
  577. # update_template(\@searched_dirs, $dir, $output_dir, $template_lang,
  578. # @source_files);
  579. # update_i18n_files(\@searched_dirs, $dir, $dir,
  580. # $template_lang, "$output_dir/$template_lang");
  581. # merge_i18n_files(\@searched_dirs, $dir, $translations_file);
  582. #}
  583. elsif ($command eq 'filter')
  584. {
  585. die "filter requires --lang\n" if (!defined($lang));
  586. $/ = undef;
  587. my $string_to_find = <STDIN>;
  588. my $translated_string = get_translation_string(\@searched_dirs, $dir, $lang, $string_to_find);
  589. $translated_string = '' if (!defined($translated_string));
  590. #print STDERR "LLL $string_to_find|$translated_string\n";
  591. if (defined($charset))
  592. { # this doesn't work, and is not needed
  593. binmode(STDOUT, ":encoding($charset)");
  594. }
  595. print STDOUT "$translated_string";
  596. }
  597. else
  598. {
  599. warn "Unknown i18n command: $command\n";
  600. }
  601. exit 0;
  602. 1;