source: trunk/gsdl/bin/script/gti.pl@ 12625

Last change on this file since 12625 was 12566, checked in by mdewsnip, 18 years ago

Added fix for warnings when submitting to a new file.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 51.6 KB
Line 
1#!/usr/bin/perl -w
2
3###########################################################################
4#
5# gti.pl
6#
7# A component of the Greenstone digital library software
8# from the New Zealand Digital Library Project at the
9# University of Waikato, New Zealand.
10#
11# Copyright (C) 2005 New Zealand Digital Library Project
12#
13# This program is free software; you can redistribute it and/or modify
14# it under the terms of the GNU General Public License as published by
15# the Free Software Foundation; either version 2 of the License, or
16# (at your option) any later version.
17#
18# This program is distributed in the hope that it will be useful,
19# but WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21# GNU General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program; if not, write to the Free Software
25# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26#
27###########################################################################
28
29
30BEGIN {
31 die "GSDLHOME not set\n" unless defined $ENV{'GSDLHOME'};
32 unshift (@INC, "$ENV{'GSDLHOME'}/perllib");
33}
34
35
36use iso639;
37use strict;
38use util;
39
40
41my $anonymous_cvs_root = ":pserver:cvs_anon\@cvs.scms.waikato.ac.nz:2402/usr/local/global-cvs/gsdl-src";
42my $gsdl_root_directory = "$ENV{'GSDLHOME'}";
43my $gti_log_file = &util::filename_cat($gsdl_root_directory, "etc", "gti.log");
44my $source_language_code = "en"; # This is non-negiotable
45
46my $gti_translation_files =
47 [ # Greenstone macrofiles
48 { 'key' => "coredm",
49 'file_type' => "macrofile",
50 'source_file' => "macros/english.dm",
51 'target_file' => "macros/{bn:bengali;fa:farsi;gd:gaelic;id:indo;lv:latvian;pt-br:port-br;pt-pt:port-pt;zh-tr:chinese-trad;iso_639_1_target_language_name}.dm" },
52 { 'key' => "auxdm",
53 'file_type' => "macrofile",
54 'source_file' => "macros/english2.dm",
55 'target_file' => "macros/{bn:bengali;fa:farsi;gd:gaelic;id:indo;lv:latvian;pt-br:port-br;pt-pt:port-pt;zh-tr:chinese-trad;iso_639_1_target_language_name}2.dm" },
56
57 # GLI dictionary
58 { 'key' => "glidict",
59 'file_type' => "resource_bundle",
60 'source_file' => "gli/classes/dictionary.properties",
61 'target_file' => "gli/classes/dictionary_{target_language_code}.properties" },
62
63 # GLI help
64 { 'key' => "glihelp",
65 'file_type' => "greenstone_xml",
66 'source_file' => "gli/help/en/help.xml",
67 'target_file' => "gli/help/{target_language_code}/help.xml" },
68
69 # Greenstone Perl modules
70 { 'key' => "perlmodules",
71 'file_type' => "resource_bundle",
72 'source_file' => "perllib/strings.properties",
73 'target_file' => "perllib/strings_{target_language_code}.properties" },
74
75 # Greenstone tutorial exercises
76 # { 'key' => "tutorials",
77 # 'file_type' => "greenstone_xml",
78 # 'source_file' => "gsdl-tutorials/tutorial_en.xml",
79 # 'target_file' => "gsdl-tutorials/tutorial_{target_language_code}.xml" },
80
81 # Greenstone.org
82 # { 'key' => "greenorg",
83 # 'file_type' => "macrofile",
84 # 'source_file' => "greenorg/macros/english.dm",
85 # 'target_file' => "greenorg/macros/{iso_639_1_target_language_name}.dm" }
86 ];
87
88
89sub main
90{
91 # Get the command to process, and any arguments
92 my $gti_command = shift(@_);
93 my @gti_command_arguments = @_;
94
95 # Open the GTI log file for appending, or write to STDERR if that fails
96 if (!open(GTI_LOG, ">>$gti_log_file")) {
97 open(GTI_LOG, ">&STDERR");
98 }
99
100 # Log the command that launched this script
101 &log_message("Command: $0 @ARGV");
102
103 # Check that a command was supplied
104 if (!$gti_command) {
105 &throw_fatal_error("Missing command.");
106 }
107
108 # Process the command
109 if ($gti_command =~ /^get-first-n-chunks-requiring-work$/i) {
110 print &get_first_n_chunks_requiring_work(@gti_command_arguments);
111 }
112 if ($gti_command =~ /^get-language-status$/i) {
113 print &get_language_status(@gti_command_arguments);
114 }
115 if ($gti_command =~ /^search-chunks$/i) {
116 print &search_chunks(@gti_command_arguments);
117 }
118 if ($gti_command =~ /^submit-translations$/i) {
119 # This command cannot produce any output since it reads input
120 &submit_translations(@gti_command_arguments);
121 }
122
123 # The command was not recognized
124 # &throw_fatal_error("Unknown command \"$gti_command\".");
125}
126
127
128sub throw_fatal_error
129{
130 my $error_message = shift(@_);
131
132 # Write an XML error response
133 print "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
134 print "<GTIResponse>\n";
135 print " <GTIError time=\"" . time() . "\">" . $error_message . "</GTIError>\n";
136 print "</GTIResponse>\n";
137
138 # Log the error message, then die
139 &log_message("Error: $error_message");
140 die "\n";
141}
142
143
144sub log_message
145{
146 my $log_message = shift(@_);
147 print GTI_LOG time() . " -- " . $log_message . "\n";
148}
149
150
151sub get_first_n_chunks_requiring_work
152{
153 # The code of the target language (ensure it is lowercase)
154 my $target_language_code = lc(shift(@_));
155 # The key of the file to translate (ensure it is lowercase)
156 my $translation_file_key = lc(shift(@_));
157 # The number of chunks to return (defaults to one if not specified)
158 my $num_chunks_to_return = shift(@_) || "1";
159
160 # Check that the necessary arguments were supplied
161 if (!$target_language_code || !$translation_file_key) {
162 &throw_fatal_error("Missing command argument.");
163 }
164
165 # Get (and check) the translation configuration
166 my ($source_file, $target_file, $translation_file_type)
167 = &get_translation_configuration($target_language_code, $translation_file_key);
168
169 # Parse the source language and target language files
170 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
171 my @source_file_lines = &read_file_lines($source_file_path);
172 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
173
174 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
175 my @target_file_lines = &read_file_lines($target_file_path);
176 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
177
178 # Filter out any automatically translated chunks
179 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
180 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
181 delete $source_file_key_to_line_mapping{$chunk_key};
182 delete $target_file_key_to_line_mapping{$chunk_key};
183 }
184 }
185
186 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
187 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
188 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
189 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
190
191 # Determine the target file chunks requiring translation
192 my @target_file_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping);
193 &log_message("Number of target chunks requiring translation: " . scalar(@target_file_keys_requiring_translation));
194
195 # Determine the target file chunks requiring updating
196 my %source_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($source_file, \@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
197 my %target_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($target_file, \@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
198 my @target_file_keys_requiring_updating = &determine_chunks_requiring_updating(\%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
199 &log_message("Number of target chunks requiring updating: " . scalar(@target_file_keys_requiring_updating));
200
201 # Form an XML response to the command
202 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
203 $xml_response .= "<GTIResponse>\n";
204 $xml_response .= " <TranslationFile"
205 . " key=\"" . $translation_file_key . "\""
206 . " target_file_path=\"" . $target_file . "\""
207 . " num_chunks_translated=\"" . (scalar(keys(%source_file_key_to_text_mapping)) - scalar(@target_file_keys_requiring_translation)) . "\""
208 . " num_chunks_requiring_translation=\"" . scalar(@target_file_keys_requiring_translation) . "\""
209 . " num_chunks_requiring_updating=\"" . scalar(@target_file_keys_requiring_updating) . "\"\/>\n";
210
211 # Do chunks requiring translation first
212 if ($num_chunks_to_return > scalar(@target_file_keys_requiring_translation)) {
213 $xml_response .= " <ChunksRequiringTranslation size=\"" . scalar(@target_file_keys_requiring_translation) . "\">\n";
214 }
215 else {
216 $xml_response .= " <ChunksRequiringTranslation size=\"" . $num_chunks_to_return . "\">\n";
217 }
218
219 foreach my $chunk_key (@target_file_keys_requiring_translation) {
220 last if ($num_chunks_to_return == 0);
221
222 my $source_file_chunk_date = $source_file_key_to_last_update_date_mapping{$chunk_key};
223 my $source_file_chunk_text = &make_text_xml_safe($source_file_key_to_text_mapping{$chunk_key});
224
225 $xml_response .= " <Chunk key=\"$chunk_key\">\n";
226 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
227 $xml_response .= " <TargetFileText></TargetFileText>\n";
228 $xml_response .= " </Chunk>\n";
229
230 $num_chunks_to_return--;
231 }
232
233 $xml_response .= " </ChunksRequiringTranslation>\n";
234
235 # Then do chunks requiring updating
236 if ($num_chunks_to_return > scalar(@target_file_keys_requiring_updating)) {
237 $xml_response .= " <ChunksRequiringUpdating size=\"" . scalar(@target_file_keys_requiring_updating) . "\">\n";
238 }
239 else {
240 $xml_response .= " <ChunksRequiringUpdating size=\"" . $num_chunks_to_return . "\">\n";
241 }
242
243 foreach my $chunk_key (@target_file_keys_requiring_updating) {
244 last if ($num_chunks_to_return == 0);
245
246 my $source_file_chunk_date = $source_file_key_to_last_update_date_mapping{$chunk_key};
247 my $source_file_chunk_text = &make_text_xml_safe($source_file_key_to_text_mapping{$chunk_key});
248 my $target_file_chunk_date = $target_file_key_to_last_update_date_mapping{$chunk_key};
249 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping{$chunk_key});
250
251 $xml_response .= " <Chunk key=\"$chunk_key\">\n";
252 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
253 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
254 $xml_response .= " </Chunk>\n";
255
256 $num_chunks_to_return--;
257 }
258
259 $xml_response .= " </ChunksRequiringUpdating>\n";
260
261 $xml_response .= "</GTIResponse>\n";
262 return $xml_response;
263}
264
265
266sub get_language_status
267{
268 # The code of the target language (ensure it is lowercase)
269 my $target_language_code = lc(shift(@_));
270
271 # Check that the necessary arguments were supplied
272 if (!$target_language_code) {
273 &throw_fatal_error("Missing command argument.");
274 }
275
276 # Form an XML response to the command
277 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
278 $xml_response .= "<GTIResponse>\n";
279 $xml_response .= " <LanguageStatus code=\"$target_language_code\">\n";
280
281 foreach my $translation_file (@$gti_translation_files) {
282 # Get (and check) the translation configuration
283 my ($source_file, $target_file, $translation_file_type)
284 = &get_translation_configuration($target_language_code, $translation_file->{'key'});
285
286 # Parse the source language and target language files
287 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
288 my @source_file_lines = &read_file_lines($source_file_path);
289 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
290
291 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
292 my @target_file_lines = &read_file_lines($target_file_path);
293 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
294
295 # Filter out any automatically translated chunks
296 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
297 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
298 delete $source_file_key_to_line_mapping{$chunk_key};
299 delete $target_file_key_to_line_mapping{$chunk_key};
300 }
301 }
302
303 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
304 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
305 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
306 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
307
308 # Determine the target file chunks requiring translation
309 my @target_file_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping);
310 &log_message("Number of target chunks requiring translation: " . scalar(@target_file_keys_requiring_translation));
311
312 # Determine the target file chunks requiring updating
313 my @target_file_keys_requiring_updating = ();
314 if (-e $target_file_path) {
315 my %source_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($source_file, \@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
316 my %target_file_key_to_last_update_date_mapping = &build_key_to_last_update_date_mapping($target_file, \@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
317 @target_file_keys_requiring_updating = &determine_chunks_requiring_updating(\%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
318 &log_message("Number of target chunks requiring updating: " . scalar(@target_file_keys_requiring_updating));
319 }
320
321 $xml_response .= " <TranslationFile"
322 . " key=\"" . $translation_file->{'key'} . "\""
323 . " target_file_path=\"" . $target_file . "\""
324 . " num_chunks_translated=\"" . (scalar(keys(%source_file_key_to_text_mapping)) - scalar(@target_file_keys_requiring_translation)) . "\""
325 . " num_chunks_requiring_translation=\"" . scalar(@target_file_keys_requiring_translation) . "\""
326 . " num_chunks_requiring_updating=\"" . scalar(@target_file_keys_requiring_updating) . "\"\/>\n";
327 }
328
329 $xml_response .= " </LanguageStatus>\n";
330
331 $xml_response .= "</GTIResponse>\n";
332 return $xml_response;
333}
334
335
336sub search_chunks
337{
338 # The code of the target language (ensure it is lowercase)
339 my $target_language_code = lc(shift(@_));
340 # The key of the file to translate (ensure it is lowercase)
341 my $translation_file_key = lc(shift(@_));
342 # The query string
343 my $query_string = join(' ', @_);
344
345 # Check that the necessary arguments were supplied
346 if (!$target_language_code || !$translation_file_key || !$query_string) {
347 &throw_fatal_error("Missing command argument.");
348 }
349
350 # Get (and check) the translation configuration
351 my ($source_file, $target_file, $translation_file_type)
352 = &get_translation_configuration($target_language_code, $translation_file_key);
353
354 # Parse the source language and target language files
355 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
356 my @source_file_lines = &read_file_lines($source_file_path);
357 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
358
359 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
360 my @target_file_lines = &read_file_lines($target_file_path);
361 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
362
363 # Filter out any automatically translated chunks
364 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
365 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
366 delete $source_file_key_to_line_mapping{$chunk_key};
367 delete $target_file_key_to_line_mapping{$chunk_key};
368 }
369 }
370
371 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
372 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
373 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
374 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
375
376 # Determine the target file chunks matching the query
377 my @target_file_keys_matching_query = ();
378 foreach my $chunk_key (keys(%target_file_key_to_text_mapping)) {
379 my $target_file_text = $target_file_key_to_text_mapping{$chunk_key};
380 if ($target_file_text =~ /$query_string/i) {
381 # &log_message("Chunk with key $chunk_key matches query.");
382 push(@target_file_keys_matching_query, $chunk_key);
383 }
384 }
385
386 # Form an XML response to the command
387 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
388 $xml_response .= "<GTIResponse>\n";
389
390 $xml_response .= " <ChunksMatchingQuery size=\"" . scalar(@target_file_keys_matching_query) . "\">\n";
391 foreach my $chunk_key (@target_file_keys_matching_query) {
392 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping{$chunk_key});
393
394 $xml_response .= " <Chunk key=\"$chunk_key\">\n";
395 $xml_response .= " <TargetFileText>$target_file_chunk_text</TargetFileText>\n";
396 $xml_response .= " </Chunk>\n";
397 }
398 $xml_response .= " </ChunksMatchingQuery>\n";
399
400 $xml_response .= "</GTIResponse>\n";
401 return $xml_response;
402}
403
404
405sub submit_translations
406{
407 # The code of the target language (ensure it is lowercase)
408 my $target_language_code = lc(shift(@_));
409 # The key of the file to translate (ensure it is lowercase)
410 my $translation_file_key = lc(shift(@_));
411 # The username of the translation submitter
412 my $submitter_username = shift(@_);
413 # Whether to submit a target chunk even if it hasn't changed
414 my $force_submission_flag = shift(@_);
415
416 # Check that the necessary arguments were supplied
417 if (!$target_language_code || !$translation_file_key || !$submitter_username) {
418 &log_message("Fatal error (but cannot be thrown): Missing command argument.");
419 die "\n";
420 }
421
422 # Get (and check) the translation configuration
423 my ($source_file, $target_file, $translation_file_type)
424 = &get_translation_configuration($target_language_code, $translation_file_key);
425
426 # Parse the source language and target language files
427 my @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
428 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
429 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
430 my %source_file_key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
431 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
432
433 my @target_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $target_file));
434 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
435 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
436 my %target_file_key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
437 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
438
439 # Submission date
440 my $day = (localtime)[3];
441 my $month = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")[(localtime)[4]];
442 my $year = (localtime)[5] + 1900;
443 my $submission_date = "$day-$month-$year";
444
445 open(SUBMISSION, "-");
446 my @submission_lines = <SUBMISSION>;
447 close(SUBMISSION);
448
449 # Remove any nasty carriage returns
450 &log_message("Submission:");
451 foreach my $submission_line (@submission_lines) {
452 $submission_line =~ s/\r$//;
453 &log_message(" $submission_line");
454 }
455
456 my %source_file_key_to_submission_mapping = ();
457 my %target_file_key_to_submission_mapping = ();
458 for (my $i = 0; $i < scalar(@submission_lines); $i++) {
459 # Read source file part of submission
460 if ($submission_lines[$i] =~ /^\<SourceFileText key=\"(.+)\"\>/) {
461 my $chunk_key = $1;
462
463 # Read the source file text
464 my $source_file_chunk_text = "";
465 $i++;
466 while ($i < scalar(@submission_lines) && $submission_lines[$i] !~ /^\<\/SourceFileText\>/) {
467 $source_file_chunk_text .= $submission_lines[$i];
468 $i++;
469 }
470 $source_file_chunk_text =~ s/\n$//; # Strip the extra newline character added
471 $source_file_chunk_text = &unmake_text_xml_safe($source_file_chunk_text);
472
473 &log_message("Source file key: $chunk_key");
474 &log_message("Source file text: $source_file_chunk_text");
475 $source_file_key_to_submission_mapping{$chunk_key} = $source_file_chunk_text;
476 }
477
478 # Read target file part of submission
479 if ($submission_lines[$i] =~ /^\<TargetFileText key=\"(.+)\"\>/) {
480 my $chunk_key = $1;
481
482 # Read the target file text
483 my $target_file_chunk_text = "";
484 $i++;
485 while ($i < scalar(@submission_lines) && $submission_lines[$i] !~ /^\<\/TargetFileText\>/) {
486 $target_file_chunk_text .= $submission_lines[$i];
487 $i++;
488 }
489 $target_file_chunk_text =~ s/\n$//; # Strip the extra newline character added
490 $target_file_chunk_text = &unmake_text_xml_safe($target_file_chunk_text);
491
492 &log_message("Target file key: $chunk_key");
493 &log_message("Target file text: $target_file_chunk_text");
494 $target_file_key_to_submission_mapping{$chunk_key} = $target_file_chunk_text;
495 }
496 }
497
498 # -----------------------------------------
499 # Validate the translation submissions
500 # -----------------------------------------
501
502 # Check that the translations are valid
503 foreach my $chunk_key (keys(%source_file_key_to_submission_mapping)) {
504 # Make sure the submitted chunk still exists in the source file
505 if (!defined($source_file_key_to_text_mapping{$chunk_key})) {
506 &log_message("Warning: Source chunk $chunk_key no longer exists (ignoring submission).");
507 delete $source_file_key_to_submission_mapping{$chunk_key};
508 delete $target_file_key_to_submission_mapping{$chunk_key};
509 next;
510 }
511
512 # Make sure the submitted source chunk matches the source file chunk
513 if ($source_file_key_to_submission_mapping{$chunk_key} ne $source_file_key_to_text_mapping{$chunk_key}) {
514 &log_message("Warning: Source chunk $chunk_key has changed (ignoring submission).");
515 &log_message("Submission source: $source_file_key_to_submission_mapping{$chunk_key}");
516 &log_message(" Source text: $source_file_key_to_text_mapping{$chunk_key}");
517 delete $source_file_key_to_submission_mapping{$chunk_key};
518 delete $target_file_key_to_submission_mapping{$chunk_key};
519 next;
520 }
521 }
522
523 # Apply the submitted translations
524 foreach my $chunk_key (keys(%target_file_key_to_submission_mapping)) {
525 # Only apply the submission if it is a change, unless -force_submission has been specified
526 if ($force_submission_flag || !defined($target_file_key_to_text_mapping{$chunk_key}) || $target_file_key_to_submission_mapping{$chunk_key} ne $target_file_key_to_text_mapping{$chunk_key}) {
527 $target_file_key_to_text_mapping{$chunk_key} = $target_file_key_to_submission_mapping{$chunk_key};
528 $target_file_key_to_gti_comment_mapping{$chunk_key} = "Updated $submission_date by $submitter_username";
529 }
530 }
531
532 eval "&write_translated_${translation_file_type}(\$source_file, \\\@source_file_lines, \\\%source_file_key_to_text_mapping, \$target_file, \\\@target_file_lines, \\\%target_file_key_to_text_mapping, \\\%target_file_key_to_gti_comment_mapping, \$target_language_code)";
533}
534
535
536sub get_translation_configuration
537{
538 # Get the code of the target language
539 my $target_language_code = shift(@_);
540 # Get the key of the file to translate
541 my $translation_file_key = shift(@_);
542
543 # Read the translation data from the gti.cfg file
544 my ($source_file, $target_file, $translation_file_type) =
545 &get_translation_data_for($target_language_code, $translation_file_key);
546
547 # Check that the file to translate is defined in the gti.cfg file
548 if (!$source_file || !$target_file || !$translation_file_type) {
549 &throw_fatal_error("Missing or incomplete specification for translation file \"$translation_file_key\" in gti.pl.");
550 }
551
552 # Check that the source file exists
553 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
554 if (!-e $source_file_path) {
555 &throw_fatal_error("Source file $source_file_path does not exist.");
556 }
557
558 # Check that the source file is up to date
559 # The "2>/dev/null" is very important! If it is missing this will never return when run from the receptionist
560 # unless ($translation_file_is_not_in_cvs) {
561 my $source_file_cvs_status = `cd $gsdl_root_directory; cvs -d $anonymous_cvs_root update $source_file 2>/dev/null`;
562 if ($source_file_cvs_status =~ /^C /) {
563 &throw_fatal_error("Source file $source_file_path conflicts with the repository.");
564 }
565 if ($source_file_cvs_status =~ /^M /) {
566 &throw_fatal_error("Source file $source_file_path contains uncommitted changes.");
567 }
568 # }
569
570 return ($source_file, $target_file, $translation_file_type);
571}
572
573
574sub get_translation_data_for
575{
576 my ($target_language_code, $translation_file_key) = @_;
577
578 foreach my $translation_file (@$gti_translation_files) {
579 # If this isn't the correct translation file, move onto the next one
580 next if ($translation_file_key ne $translation_file->{'key'});
581
582 # Resolve the target language file
583 my $target_language_file = $translation_file->{'target_file'};
584 if ($target_language_file =~ /(\{.+\;.+\})/) {
585 my $unresolved_target_language_file_part = $1;
586
587 # Check for a special case for the target language code
588 if ($unresolved_target_language_file_part =~ /(\{|\;)$target_language_code:([^\;]+)(\;|\})/) {
589 my $resolved_target_language_file_part = $2;
590 $target_language_file =~ s/$unresolved_target_language_file_part/$resolved_target_language_file_part/;
591 }
592 # Otherwise use the last part as the default value
593 else {
594 my ($default_target_language_file_part) = $unresolved_target_language_file_part =~ /([^\;]+)\}/;
595 $target_language_file =~ s/$unresolved_target_language_file_part/\{$default_target_language_file_part\}/;
596 }
597 }
598
599 # Resolve instances of {iso_639_1_target_language_name}
600 my $iso_639_1_target_language_name = $iso639::fromiso639{$target_language_code};
601 $iso_639_1_target_language_name =~ tr/A-Z/a-z/ if $iso_639_1_target_language_name;
602 $target_language_file =~ s/\{iso_639_1_target_language_name\}/$iso_639_1_target_language_name/g;
603
604 # Resolve instances of {target_language_code}
605 $target_language_file =~ s/\{target_language_code\}/$target_language_code/g;
606
607 return ($translation_file->{'source_file'}, $target_language_file, $translation_file->{'file_type'});
608 }
609
610 return ();
611}
612
613
614sub read_file_lines
615{
616 my ($file_path) = @_;
617
618 if (!open(FILE_IN, "<$file_path")) {
619 &log_message("Note: Could not open file $file_path.");
620 return ();
621 }
622 my @file_lines = <FILE_IN>;
623 close(FILE_IN);
624
625 return @file_lines;
626}
627
628
629sub build_key_to_line_mapping
630{
631 my ($file_lines, $translation_file_type) = @_;
632 eval "return &build_key_to_line_mapping_for_${translation_file_type}(\@\$file_lines)";
633}
634
635
636sub build_key_to_text_mapping
637{
638 my ($file_lines, $key_to_line_mapping, $translation_file_type) = @_;
639
640 my %key_to_text_mapping = ();
641 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
642 my $chunk_starting_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[0];
643 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[1];
644
645 my $chunk_text = @$file_lines[$chunk_starting_line];
646 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
647 $chunk_text .= @$file_lines[$l];
648 }
649
650 # Map from chunk key to text
651 eval "\$key_to_text_mapping{\${chunk_key}} = &import_chunk_from_${translation_file_type}(\$chunk_text)";
652 }
653
654 return %key_to_text_mapping;
655}
656
657
658sub build_key_to_last_update_date_mapping
659{
660 my ($file, $file_lines, $key_to_line_mapping, $translation_file_type) = @_;
661
662 # If the files aren't in CVS then we can't tell anything about what needs updating
663 # return () if ($translation_file_is_not_in_cvs);
664
665 # Build a mapping from key to CVS date
666 # Need to be careful with this mapping because the chunk keys won't necessarily all be valid
667 my %key_to_cvs_date_mapping = &build_key_to_cvs_date_mapping($file, $translation_file_type);
668
669 # Build a mapping from key to comment date
670 my %key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping($file_lines, $key_to_line_mapping, $translation_file_type);
671
672 # Build a mapping from key to last update date (the latter of the CVS date and comment date)
673 my %key_to_last_update_date_mapping = ();
674 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
675 # Use the CVS date as a starting point
676 my $chunk_cvs_date = $key_to_cvs_date_mapping{$chunk_key};
677 $key_to_last_update_date_mapping{$chunk_key} = $chunk_cvs_date;
678
679 # If a comment date exists and it is after the CVS date, use that instead
680 my $chunk_gti_comment = $key_to_gti_comment_mapping{$chunk_key};
681 if (defined($chunk_gti_comment) && $chunk_gti_comment =~ /(\d?\d-\D\D\D-\d\d\d\d)/) {
682 my $chunk_comment_date = $1;
683 if ((!defined($chunk_cvs_date) || &is_date_after($chunk_comment_date, $chunk_cvs_date))) {
684 $key_to_last_update_date_mapping{$chunk_key} = $chunk_comment_date;
685 }
686 }
687 }
688
689 return %key_to_last_update_date_mapping;
690}
691
692
693sub build_key_to_cvs_date_mapping
694{
695 my ($filename, $translation_file_type) = @_;
696
697 # Use CVS to annotate each line of the file with the date it was last edited
698 # The "2>/dev/null" is very important! If it is missing this will never return when run from the receptionist
699 my $cvs_annotated_file = `cd $gsdl_root_directory; cvs -d $anonymous_cvs_root annotate -F $filename 2>/dev/null`;
700 my @cvs_annotated_file_lines = split(/\n/, $cvs_annotated_file);
701
702 my @cvs_annotated_file_lines_date = ();
703 foreach my $cvs_annotated_file_line (@cvs_annotated_file_lines) {
704 # Extract the date from the CVS annotation at the front
705 $cvs_annotated_file_line =~ s/^\S+\s+\(\S+\s+(\S+)\):\s//;
706 push(@cvs_annotated_file_lines_date, $1);
707 }
708
709 # Build a key to line mapping for the CVS annotated file, for matching the chunk key to the CVS date
710 my %key_to_line_mapping = &build_key_to_line_mapping(\@cvs_annotated_file_lines, $translation_file_type);
711
712 my %key_to_cvs_date_mapping = ();
713 foreach my $chunk_key (keys(%key_to_line_mapping)) {
714 my $chunk_starting_line = (split(/-/, $key_to_line_mapping{$chunk_key}))[0];
715 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping{$chunk_key}))[1];
716
717 # Find the date this chunk was last edited, from the CVS annotation
718 my $chunk_date = $cvs_annotated_file_lines_date[$chunk_starting_line];
719 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
720 if (&is_date_after($cvs_annotated_file_lines_date[$l], $chunk_date)) {
721 # This part of the chunk has been updated more recently
722 $chunk_date = $cvs_annotated_file_lines_date[$l];
723 }
724 }
725
726 # Map from chunk key to CVS date
727 $key_to_cvs_date_mapping{$chunk_key} = $chunk_date;
728 }
729
730 return %key_to_cvs_date_mapping;
731}
732
733
734sub build_key_to_gti_comment_mapping
735{
736 my ($file_lines, $key_to_line_mapping, $translation_file_type) = @_;
737
738 my %key_to_gti_comment_mapping = ();
739 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
740 my $chunk_starting_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[0];
741 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[1];
742
743 my $chunk_text = @$file_lines[$chunk_starting_line];
744 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
745 $chunk_text .= @$file_lines[$l];
746 }
747
748 # Map from chunk key to GTI comment
749 my $chunk_gti_comment;
750 eval "\$chunk_gti_comment = &get_${translation_file_type}_chunk_gti_comment(\$chunk_text)";
751 $key_to_gti_comment_mapping{$chunk_key} = $chunk_gti_comment if (defined($chunk_gti_comment));
752 }
753
754 return %key_to_gti_comment_mapping;
755}
756
757
758sub determine_chunks_requiring_translation
759{
760 my $source_file_key_to_text_mapping = shift(@_);
761 my $target_file_key_to_text_mapping = shift(@_);
762
763 # Chunks needing translation are those in the source file with no translation in the target file
764 my @target_file_keys_requiring_translation = ();
765 foreach my $chunk_key (keys(%$source_file_key_to_text_mapping)) {
766 if ($source_file_key_to_text_mapping->{$chunk_key} && !$target_file_key_to_text_mapping->{$chunk_key}) {
767 # &log_message("Chunk with key $chunk_key needs translating.");
768 push(@target_file_keys_requiring_translation, $chunk_key);
769 }
770 }
771
772 return @target_file_keys_requiring_translation;
773}
774
775
776sub determine_chunks_requiring_updating
777{
778 my $source_file_key_to_last_update_date_mapping = shift(@_);
779 my $target_file_key_to_last_update_date_mapping = shift(@_);
780
781 # Chunks needing updating are those in the target file that have been more recently edited in the source file
782 my @target_file_keys_requiring_updating = ();
783 foreach my $chunk_key (keys(%$source_file_key_to_last_update_date_mapping)) {
784 my $source_chunk_last_update_date = $source_file_key_to_last_update_date_mapping->{$chunk_key};
785 my $target_chunk_last_update_date = $target_file_key_to_last_update_date_mapping->{$chunk_key};
786 if (defined($target_chunk_last_update_date) && &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date)) {
787 # &log_message("Chunk with key $chunk_key needs updating.");
788 push(@target_file_keys_requiring_updating, $chunk_key);
789 }
790 }
791
792 return @target_file_keys_requiring_updating;
793}
794
795
796sub is_chunk_automatically_translated
797{
798 my ($chunk_key, $translation_file_type) = @_;
799 eval "return &is_${translation_file_type}_chunk_automatically_translated(\$chunk_key)";
800}
801
802
803sub make_text_xml_safe
804{
805 my $text = shift(@_);
806 $text =~ s/\&/\&amp\;/g;
807 $text =~ s/\&amp\;lt\;/\&amp\;amp\;lt\;/g;
808 $text =~ s/\&amp\;gt\;/\&amp\;amp\;gt\;/g;
809 $text =~ s/\&amp\;rarr\;/\&amp\;amp\;rarr\;/g;
810 $text =~ s/\&amp\;mdash\;/\&amp\;amp\;mdash\;/g;
811 $text =~ s/</\&lt\;/g;
812 $text =~ s/>/\&gt\;/g;
813 return $text;
814}
815
816
817sub unmake_text_xml_safe
818{
819 my $text = shift(@_);
820 $text =~ s/\&lt\;/</g;
821 $text =~ s/\&gt\;/>/g;
822 $text =~ s/\&amp\;/\&/g;
823 return $text;
824}
825
826
827# Returns 1 if $date1 is after $date2, 0 otherwise
828sub is_date_after
829{
830 my ($date1, $date2) = @_;
831 my %months = ("Jan", 1, "Feb", 2, "Mar", 3, "Apr", 4, "May", 5, "Jun", 6,
832 "Jul", 7, "Aug", 8, "Sep", 9, "Oct", 10, "Nov", 11, "Dec", 12);
833
834 my @date1parts = split(/-/, $date1);
835 my @date2parts = split(/-/, $date2);
836
837 # Compare year - nasty because we have rolled over into a new century
838 my $year1 = $date1parts[2];
839 if ($year1 < 80) {
840 $year1 += 2000;
841 }
842 my $year2 = $date2parts[2];
843 if ($year2 < 80) {
844 $year2 += 2000;
845 }
846
847 # Compare year
848 if ($year1 > $year2) {
849 return 1;
850 }
851 elsif ($year1 == $year2) {
852 # Year is the same, so compare month
853 if ($months{$date1parts[1]} > $months{$date2parts[1]}) {
854 return 1;
855 }
856 elsif ($months{$date1parts[1]} == $months{$date2parts[1]}) {
857 # Month is the same, so compare day
858 if ($date1parts[0] > $date2parts[0]) {
859 return 1;
860 }
861 }
862 }
863
864 return 0;
865}
866
867
868# ==========================================================================================
869# MACROFILE FUNCTIONS
870
871sub build_key_to_line_mapping_for_macrofile
872{
873 my (@file_lines) = @_;
874
875 my $macro_package;
876 my %chunk_key_to_line_mapping = ();
877 # Process the contents of the file, line by line
878 for (my $i = 0; $i < scalar(@file_lines); $i++) {
879 my $line = $file_lines[$i];
880 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
881
882 # Check if a new package is being defined
883 if ($line =~ m/^package\s+(.+)/) {
884 $macro_package = $1;
885 }
886
887 # Line contains a macro name
888 elsif ($line =~ m/^(_\w+_)/) {
889 my $macro_key = $1;
890 $line =~ s/\s*([^\\]\#[^\}]+)?$//; # Remove any comments and nasty whitespace
891
892 # While there is still text of the macro to go...
893 my $startline = $i;
894 while ($line !~ /\}$/) {
895 $i++;
896 if ($i == scalar(@file_lines)) {
897 &throw_fatal_error("Could not find end of macro $macro_key.");
898 }
899 $line = $file_lines[$i];
900 $line =~ s/\s*([^\\]\#[^\}]+)?$//; # Remove any comments and nasty whitespace
901 }
902
903 # The chunk key consists of the package name and the macro key
904 my $chunk_key = $macro_package . "." . $macro_key;
905 # Map from chunk key to line
906 $chunk_key_to_line_mapping{$chunk_key} = $startline . "-" . $i;
907 }
908
909 # Icon: line in format ## "image text" ## image_type ## macro_name ##
910 elsif ($line =~ m/^\#\# .* \#\# .* \#\# (.*) \#\#/) {
911 # The chunk key consists of package name and macro key
912 my $chunk_key = $macro_package . "." . $1;
913 # Map from chunk key to line
914 $chunk_key_to_line_mapping{$chunk_key} = $i . "-" . $i;
915 }
916 }
917
918 return %chunk_key_to_line_mapping;
919}
920
921
922sub import_chunk_from_macrofile
923{
924 my ($chunk_text) = @_;
925
926 # Is this an icon macro??
927 if ($chunk_text =~ /^\#\# (.*)/) {
928 # Extract image macro text
929 $chunk_text =~ /^\#\#\s+([^\#]+)\s+\#\#/;
930 $chunk_text = $1;
931
932 # Remove enclosing quotes
933 $chunk_text =~ s/^\"//;
934 $chunk_text =~ s/\"$//;
935 }
936
937 # No, so it must be a text macro
938 else {
939 # Remove macro key
940 $chunk_text =~ s/^_([^_]+)_(\s*)//;
941
942 # Remove language specifier
943 $chunk_text =~ s/^\[l=.*\](\s*)//;
944
945 # Remove braces enclosing text
946 $chunk_text =~ s/^{(\s*)((.|\n)*)}(\s*)(\#.+\s*)?/$2/;
947 }
948
949 return $chunk_text;
950}
951
952
953sub get_macrofile_chunk_gti_comment
954{
955 my ($chunk_text) = @_;
956
957 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
958 if ($chunk_text =~ /\#\s+(Updated\s+\d?\d-\D\D\D-\d\d\d\d.*)\s*$/i) {
959 return $1;
960 }
961
962 return undef;
963}
964
965
966sub is_macrofile_chunk_automatically_translated
967{
968 my ($chunk_key) = @_;
969
970 # The _httpiconX_, _widthX_ and _heightX_ image macros are automatically translated
971 if ($chunk_key =~ /\._(httpicon|width|height)/) {
972 return 1;
973 }
974
975 return 0;
976}
977
978
979# Use the source file to generate a target file that is formatted the same
980sub write_translated_macrofile
981{
982 my $source_file = shift(@_); # Not used
983 my @source_file_lines = @{shift(@_)};
984 my $source_file_key_to_text_mapping = shift(@_);
985 my $target_file = shift(@_);
986 my @target_file_lines = @{shift(@_)};
987 my $target_file_key_to_text_mapping = shift(@_);
988 my $target_file_key_to_gti_comment_mapping = shift(@_);
989 my $target_language_code = shift(@_);
990
991 # Build a mapping from source file line to chunk key
992 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_macrofile(@source_file_lines);
993 my %source_file_line_to_key_mapping = ();
994 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
995 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
996 }
997 my @source_file_line_keys = (sort sort_by_line (keys(%source_file_line_to_key_mapping)));
998 my $source_file_line_number = 0;
999
1000 # Build a mapping from target file line to chunk key
1001 my %target_file_key_to_line_mapping = &build_key_to_line_mapping_for_macrofile(@target_file_lines);
1002 my %target_file_line_to_key_mapping = ();
1003 foreach my $chunk_key (keys(%target_file_key_to_line_mapping)) {
1004 $target_file_line_to_key_mapping{$target_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1005 }
1006 my @target_file_line_keys = (sort sort_by_line (keys(%target_file_line_to_key_mapping)));
1007
1008 # Write the new target file
1009 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1010 if (!open(TARGET_FILE, ">$target_file_path")) {
1011 &throw_fatal_error("Could not write target file $target_file_path.");
1012 }
1013
1014 # Use the header from the target file, to keep language and author information
1015 if (scalar(@target_file_line_keys) > 0) {
1016 my $target_file_line_number = 0;
1017 my $target_file_chunk_starting_line_number = (split(/-/, $target_file_line_keys[0]))[0];
1018 while ($target_file_line_number < $target_file_chunk_starting_line_number) {
1019 my $target_file_line = $target_file_lines[$target_file_line_number];
1020 last if ($target_file_line =~ /^\# -- Missing translation: /); # We don't want to get into the macros
1021 print TARGET_FILE $target_file_line;
1022 $target_file_line_number++;
1023 }
1024
1025 $source_file_line_number = (split(/-/, $source_file_line_keys[0]))[0];
1026 }
1027
1028 # Model the new target file on the source file, with the target file translations
1029 foreach my $line_key (@source_file_line_keys) {
1030 # Fill in the gaps before this chunk starts
1031 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1032 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1033 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1034 print TARGET_FILE $source_file_lines[$source_file_line_number];
1035 $source_file_line_number++;
1036 }
1037 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1038
1039 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1040 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1041 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1042
1043 my $macrofile_key = $chunk_key;
1044 $macrofile_key =~ s/^(.+?)\.//;
1045
1046 # If no translation exists for this chunk, show this, and move on
1047 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1048 print TARGET_FILE "# -- Missing translation: $macrofile_key\n";
1049 next;
1050 }
1051
1052 # Grab the source chunk text
1053 my $source_file_chunk = $source_file_lines[$source_file_chunk_starting_line_number];
1054 for (my $l = ($source_file_chunk_starting_line_number + 1); $l <= $source_file_chunk_finishing_line_number; $l++) {
1055 $source_file_chunk .= $source_file_lines[$l];
1056 }
1057
1058 # Is this an icon macro??
1059 if ($source_file_chunk =~ /^\#\# (.*)/) {
1060 # Escape any newline and question mark characters so the source text is replaced correctly
1061 $source_file_chunk_text =~ s/\\/\\\\/g;
1062 $source_file_chunk_text =~ s/\?/\\\?/g;
1063
1064 # Build the new target chunk from the source chunk
1065 my $target_file_chunk = $source_file_chunk;
1066 $target_file_chunk =~ s/$source_file_chunk_text/$target_file_chunk_text/;
1067 $target_file_chunk =~ s/(\s)*$//;
1068 print TARGET_FILE "$target_file_chunk";
1069 }
1070
1071 # No, it is just a normal text macro
1072 else {
1073 print TARGET_FILE "$macrofile_key [l=$target_language_code] {$target_file_chunk_text}";
1074 }
1075
1076 # Add the "updated" comment, if one exists
1077 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1078 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$chunk_key};
1079 }
1080 print TARGET_FILE "\n";
1081 }
1082
1083 close(TARGET_FILE);
1084}
1085
1086
1087sub sort_by_line
1088{
1089 return ((split(/-/, $a))[0] <=> (split(/-/, $b))[0]);
1090}
1091
1092
1093# ==========================================================================================
1094# RESOURCE BUNDLE FUNCTIONS
1095
1096sub build_key_to_line_mapping_for_resource_bundle
1097{
1098 my (@file_lines) = @_;
1099
1100 my %chunk_key_to_line_mapping = ();
1101 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1102 my $line = $file_lines[$i];
1103 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1104
1105 # Line contains a dictionary string
1106 if ($line =~ /^(\S+?):(.*)$/) {
1107 my $chunk_key = $1;
1108
1109 # Map from chunk key to line
1110 $chunk_key_to_line_mapping{$chunk_key} = $i . "-" . $i;
1111 }
1112 }
1113
1114 return %chunk_key_to_line_mapping;
1115}
1116
1117
1118sub import_chunk_from_resource_bundle
1119{
1120 my ($chunk_text) = @_;
1121
1122 # Simple: just remove string key
1123 $chunk_text =~ s/^(\S+?):(\s*)//;
1124 $chunk_text =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1125 $chunk_text =~ s/(\s*)\#\s+Updated\s+(\d?\d-\D\D\D-\d\d\d\d.*)\s*$//i;
1126
1127 return $chunk_text;
1128}
1129
1130
1131sub get_resource_bundle_chunk_gti_comment
1132{
1133 my ($chunk_text) = @_;
1134
1135 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1136 if ($chunk_text =~ /\#\s+(Updated\s+\d?\d-\D\D\D-\d\d\d\d.*)\s*$/i) {
1137 return $1;
1138 }
1139
1140 return undef;
1141}
1142
1143
1144sub is_resource_bundle_chunk_automatically_translated
1145{
1146 # No resource bundle chunks are automatically translated
1147 return 0;
1148}
1149
1150
1151sub write_translated_resource_bundle
1152{
1153 my $source_file = shift(@_); # Not used
1154 my @source_file_lines = @{shift(@_)};
1155 my $source_file_key_to_text_mapping = shift(@_);
1156 my $target_file = shift(@_);
1157 my @target_file_lines = @{shift(@_)}; # Not used
1158 my $target_file_key_to_text_mapping = shift(@_);
1159 my $target_file_key_to_gti_comment_mapping = shift(@_);
1160 my $target_language_code = shift(@_); # Not used
1161
1162 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1163 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_resource_bundle(@source_file_lines);
1164 my %source_file_line_to_key_mapping = ();
1165 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1166 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1167 }
1168
1169 # Write the new target file
1170 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1171 if (!open(TARGET_FILE, ">$target_file_path")) {
1172 &throw_fatal_error("Could not write target file $target_file_path.");
1173 }
1174
1175 # Model the new target file on the source file, with the target file translations
1176 my $source_file_line_number = 0;
1177 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1178 # Fill in the gaps before this chunk starts
1179 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1180 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1181 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1182 print TARGET_FILE $source_file_lines[$source_file_line_number];
1183 $source_file_line_number++;
1184 }
1185 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1186
1187 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1188 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1189 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1190
1191 # If no translation exists for this chunk, show this, and move on
1192 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1193 print TARGET_FILE "# -- Missing translation: $chunk_key\n";
1194 next;
1195 }
1196
1197 print TARGET_FILE "$chunk_key:$target_file_chunk_text";
1198 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1199 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$chunk_key};
1200 }
1201 print TARGET_FILE "\n";
1202 }
1203
1204 close(TARGET_FILE);
1205}
1206
1207
1208# ==========================================================================================
1209# GREENSTONE XML FUNCTIONS
1210
1211sub build_key_to_line_mapping_for_greenstone_xml
1212{
1213 my (@file_lines) = @_;
1214
1215 my %chunk_key_to_line_mapping = ();
1216 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1217 my $line = $file_lines[$i];
1218 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1219
1220 # Line contains a string to translate
1221 if ($line =~ /^\s*<Text id=\"(.*?)\">/) {
1222 my $chunk_key = $1;
1223 $line =~ s/\s*$//; # Remove any nasty whitespace
1224 $line =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1225
1226 # While there is still text of the string to go...
1227 my $startline = $i;
1228 while ($line !~ /<\/Text>$/) {
1229 $i++;
1230 if ($i == scalar(@file_lines)) {
1231 &throw_fatal_error("Could not find end of string $chunk_key.");
1232 }
1233 $line = $file_lines[$i];
1234 $line =~ s/\s*$//; # Remove any nasty whitespace
1235 $line =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1236 }
1237
1238 # Map from chunk key to line
1239 if (!defined($chunk_key_to_line_mapping{$chunk_key})) {
1240 $chunk_key_to_line_mapping{$chunk_key} = $startline . "-" . $i;
1241 }
1242 else {
1243 &throw_fatal_error("Duplicate key $chunk_key.");
1244 }
1245 }
1246 }
1247
1248 return %chunk_key_to_line_mapping;
1249}
1250
1251
1252sub import_chunk_from_greenstone_xml
1253{
1254 my ($chunk_text) = @_;
1255
1256 # Simple: just remove the Text tags
1257 $chunk_text =~ s/^\s*<Text id=\"(.*?)\">(\s*)//;
1258 $chunk_text =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1259 $chunk_text =~ s/<\/Text>$//;
1260
1261 return $chunk_text;
1262}
1263
1264
1265sub get_greenstone_xml_chunk_gti_comment
1266{
1267 my ($chunk_text) = @_;
1268
1269 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1270 if ($chunk_text =~ /<Updated date=\"(\d?\d-\D\D\D-\d\d\d\d.*)\"\/>$/i) {
1271 return $1;
1272 }
1273
1274 return undef;
1275}
1276
1277
1278sub is_greenstone_xml_chunk_automatically_translated
1279{
1280 # No greenstone XML chunks are automatically translated
1281 return 0;
1282}
1283
1284
1285sub write_translated_greenstone_xml
1286{
1287 my $source_file = shift(@_); # Not used
1288 my @source_file_lines = @{shift(@_)};
1289 my $source_file_key_to_text_mapping = shift(@_);
1290 my $target_file = shift(@_);
1291 my @target_file_lines = @{shift(@_)}; # Not used
1292 my $target_file_key_to_text_mapping = shift(@_);
1293 my $target_file_key_to_gti_comment_mapping = shift(@_);
1294 my $target_language_code = shift(@_); # Not used
1295
1296 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1297 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_greenstone_xml(@source_file_lines);
1298 my %source_file_line_to_key_mapping = ();
1299 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1300 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1301 }
1302
1303 # Write the new target file
1304 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1305 if (!open(TARGET_FILE, ">$target_file_path")) {
1306 &throw_fatal_error("Could not write target file $target_file_path.");
1307 }
1308
1309 # Model the new target file on the source file, with the target file translations
1310 my $source_file_line_number = 0;
1311 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1312 # Fill in the gaps before this chunk starts
1313 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1314 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1315 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1316 print TARGET_FILE $source_file_lines[$source_file_line_number];
1317 $source_file_line_number++;
1318 }
1319 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1320
1321 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1322 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1323 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1324 $target_file_chunk_text =~ s/(\n)*$//g;
1325
1326 # If no translation exists for this chunk, show this, and move on
1327 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1328 print TARGET_FILE "<!-- Missing translation: $chunk_key -->\n";
1329 next;
1330 }
1331
1332 print TARGET_FILE "<Text id=\"$chunk_key\">$target_file_chunk_text</Text>";
1333 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1334 my $chunk_gti_comment = $target_file_key_to_gti_comment_mapping->{$chunk_key};
1335 $chunk_gti_comment =~ s/^Updated //;
1336 print TARGET_FILE "<Updated date=\"" . $chunk_gti_comment . "\"\/>";
1337 }
1338 print TARGET_FILE "\n";
1339 }
1340
1341 # Fill in the end of the file
1342 while ($source_file_line_number < scalar(@source_file_lines)) {
1343 print TARGET_FILE $source_file_lines[$source_file_line_number];
1344 $source_file_line_number++;
1345 }
1346
1347 close(TARGET_FILE);
1348}
1349
1350
1351&main(@ARGV);
Note: See TracBrowser for help on using the repository browser.