source: main/trunk/greenstone2/bin/script/gti.pl@ 26441

Last change on this file since 26441 was 25285, checked in by ak19, 12 years ago

Updated to deal with paperspast.dm: added a new module into gti.pl for this. Because getting chunks from the paperspast.dm file results in attribute like values in the source and target strings, there are now functions in ApplyXSLT.java to remove and retrieve these. Finally, the function to remove these attribute-like values in the source and target strings is called in the gti-generate-tmx-xml.xslt file.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 83.8 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
40my $gsdl_root_directory = "$ENV{'GSDLHOME'}";
41my $gti_log_file = &util::filename_cat($gsdl_root_directory, "etc", "gti.log");
42my $source_language_code = "en"; # This is non-negiotable
43
44my $gti_translation_files =
45[ # Greenstone macrofiles
46{ 'key' => "coredm",
47 'file_type' => "macrofile",
48 'source_file' => "macros/english.dm",
49 '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" },
50
51{ 'key' => "auxdm",
52 'file_type' => "macrofile",
53 'source_file' => "macros/english2.dm",
54 '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" },
55
56{ 'key' => "paperspastdm",
57 'file_type' => "macrofile",
58 'source_file' => "macros/paperspast-english.dm",
59 'target_file' => "macros/paperspast-{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" },
60
61# GLI dictionary
62{ 'key' => "glidict",
63 'file_type' => "resource_bundle",
64 'source_file' => "gli/classes/dictionary.properties",
65 'target_file' => "gli/classes/dictionary_{target_language_code}.properties" },
66
67# GLI help
68{ 'key' => "glihelp",
69 'file_type' => "greenstone_xml",
70 'source_file' => "gli/help/en/help.xml",
71 'target_file' => "gli/help/{target_language_code}/help.xml" },
72
73# Greenstone Perl modules
74{ 'key' => "perlmodules",
75 'file_type' => "resource_bundle",
76 'source_file' => "perllib/strings.properties",
77 'target_file' => "perllib/strings_{target_language_code}.properties" },
78
79# Greenstone tutorial exercises
80# { 'key' => "tutorials",
81# 'file_type' => "greenstone_xml",
82# 'source_file' => "gsdl-documentation/tutorials/xml-source/tutorial_en.xml",
83# 'target_file' => "gsdl-documentation/tutorials/xml-source/tutorial_{target_language_code}.xml" },
84
85# new Greenstone.org
86{ 'key' => "greenorg",
87 'file_type' => "resource_bundle",
88 'source_file' => "greenstoneorg/website/classes/Gsc.properties",
89 'target_file' => "greenstoneorg/website/classes/Gsc_{target_language_code}.properties"
90}
91
92# { 'key' => "gs3interface",
93#'file_type' => "resource_bundle",
94#'source_file' => "greenstone3",
95#'target_file' => "greenstone3"
96#}
97];
98
99my @gs3_interface_files = ("AbstractBrowse", "AbstractGS2FieldSearch", "Authentication", "CrossCollectionSearch", "GATEServices", "GS2Construct", "GS2LuceneSearch", "interface_default", "interface_nzdl", "IViaSearch", "LuceneSearch", "MapRetrieve", "MapSearch", "metadata_names", "PhindPhraseBrowse", "QBRWebServicesHelp", "Visualizer");
100
101
102sub main
103{
104 # Get the command to process, and any arguments
105 my $gti_command = shift(@_);
106 my @gti_command_arguments = @_;
107 my $module = $_[1];
108
109 # Open the GTI log file for appending, or write to STDERR if that fails
110 if (!open(GTI_LOG, ">>$gti_log_file")) {
111 open(GTI_LOG, ">&STDERR");
112 }
113
114 # Log the command that launched this script
115 &log_message("Command: $0 @ARGV");
116
117 # Check that a command was supplied
118 if (!$gti_command) {
119 &throw_fatal_error("Missing command.");
120 }
121
122 # Process the command
123 if ($gti_command =~ /^get-all-chunks$/i) {
124 # Check that GS3 interface is the target
125 if ($module eq "gs3interface") {
126 print &get_all_chunks_for_gs3(@gti_command_arguments);
127 } else {
128 print &get_all_chunks(@gti_command_arguments);
129 }
130 }
131 elsif ($gti_command =~ /^get-first-n-chunks-requiring-work$/i) {
132 if ($module eq "gs3interface") {
133 print &get_first_n_chunks_requiring_work_for_gs3(@gti_command_arguments);
134 } else {
135 print &get_first_n_chunks_requiring_work(@gti_command_arguments);
136 }
137 }
138 elsif ($gti_command =~ /^get-uptodate-chunks$/i) {
139 if ($module eq "gs3interface") {
140 print &get_uptodate_chunks_for_gs3(@gti_command_arguments);
141 } else {
142 print &get_uptodate_chunks(@gti_command_arguments);
143 }
144 }
145 elsif ($gti_command =~ /^get-language-status$/i) {
146 print &get_language_status(@gti_command_arguments);
147 }
148 elsif ($gti_command =~ /^search-chunks$/i) {
149 print &search_chunks(@gti_command_arguments);
150 }
151 elsif ($gti_command =~ /^submit-translations$/i) {
152 # This command cannot produce any output since it reads input
153 &submit_translations(@gti_command_arguments);
154 }
155 elsif ($gti_command =~ /^create-glihelp-zip-file$/i) {
156 # This command cannot produce any output since it reads input
157 &create_glihelp_zip_file(@gti_command_arguments);
158 }
159 else {
160 # The command was not recognized
161 &throw_fatal_error("Unknown command \"$gti_command\".");
162 }
163}
164
165
166sub throw_fatal_error
167{
168 my $error_message = shift(@_);
169
170 # Write an XML error response
171 print "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
172 print "<GTIResponse>\n";
173 print " <GTIError time=\"" . time() . "\">" . $error_message . "</GTIError>\n";
174 print "</GTIResponse>\n";
175
176 # Log the error message, then die
177 &log_message("Error: $error_message");
178 die "\n";
179}
180
181
182sub log_message
183{
184 my $log_message = shift(@_);
185 print GTI_LOG time() . " -- " . $log_message . "\n";
186}
187
188
189sub get_all_chunks
190{
191 # The code of the target language (ensure it is lowercase)
192 my $target_language_code = lc(shift(@_));
193 # The key of the file to translate (ensure it is lowercase)
194 my $translation_file_key = lc(shift(@_));
195
196 # Check that the necessary arguments were supplied
197 if (!$target_language_code || !$translation_file_key) {
198 &throw_fatal_error("Missing command argument.");
199 }
200
201 # Get (and check) the translation configuration
202 my ($source_file, $target_file, $translation_file_type)
203 = &get_translation_configuration($target_language_code, $translation_file_key);
204
205 # Parse the source language and target language files
206 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
207 my @source_file_lines = &read_file_lines($source_file_path);
208 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
209
210 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
211 my @target_file_lines = &read_file_lines($target_file_path);
212 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
213
214 # Filter out any automatically translated chunks
215 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
216 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
217 delete $source_file_key_to_line_mapping{$chunk_key};
218 delete $target_file_key_to_line_mapping{$chunk_key};
219 }
220 }
221
222 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
223 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
224 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
225 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
226
227 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);
228 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);
229
230 my $xml_response = &create_xml_response_for_all_chunks($translation_file_key, $target_file, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping, \%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
231
232 return $xml_response;
233}
234
235
236sub get_uptodate_chunks
237{
238 # The code of the target language (ensure it is lowercase)
239 my $target_language_code = lc(shift(@_));
240 # The key of the file to translate (ensure it is lowercase)
241 my $translation_file_key = lc(shift(@_));
242
243 # Check that the necessary arguments were supplied
244 if (!$target_language_code || !$translation_file_key) {
245 &throw_fatal_error("Missing command argument.");
246 }
247
248 # Get (and check) the translation configuration
249 my ($source_file, $target_file, $translation_file_type)
250 = &get_translation_configuration($target_language_code, $translation_file_key);
251
252 # Parse the source language and target language files
253 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
254 my @source_file_lines = &read_file_lines($source_file_path);
255 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
256
257 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
258 my @target_file_lines = &read_file_lines($target_file_path);
259 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
260
261 # Filter out any automatically translated chunks
262 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
263 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
264 delete $source_file_key_to_line_mapping{$chunk_key};
265 delete $target_file_key_to_line_mapping{$chunk_key};
266 }
267 }
268
269 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
270 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
271 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
272 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
273
274 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);
275 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);
276
277
278 # Chunks needing updating are those in the target file that have been more recently edited in the source file
279 # All others are uptodate (which implies that they have certainly been translated at some point and would not be empty)
280 my @uptodate_target_file_keys = ();
281 foreach my $chunk_key (keys(%source_file_key_to_last_update_date_mapping)) {
282 my $source_chunk_last_update_date = $source_file_key_to_last_update_date_mapping{$chunk_key};
283 my $target_chunk_last_update_date = $target_file_key_to_last_update_date_mapping{$chunk_key};
284
285 # print "key: $chunk_key\nsource date : $source_chunk_last_update_date\ntarget date : $target_chunk_last_update_date\nafter? ". &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date) . "\n\n";
286
287 if (defined($target_chunk_last_update_date) && !&is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date)) {
288 # &log_message("Chunk with key $chunk_key needs updating.");
289 push(@uptodate_target_file_keys, $chunk_key);
290 }
291 }
292
293 my $xml_response = &create_xml_response_for_uptodate_chunks($translation_file_key, $target_file, \@uptodate_target_file_keys, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping, \%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
294
295 return $xml_response;
296}
297
298
299sub get_first_n_chunks_requiring_work
300{
301 # The code of the target language (ensure it is lowercase)
302 my $target_language_code = lc(shift(@_));
303 # The key of the file to translate (ensure it is lowercase)
304 my $translation_file_key = lc(shift(@_));
305 # The number of chunks to return (defaults to one if not specified)
306 my $num_chunks_to_return = shift(@_) || "1";
307
308 # Check that the necessary arguments were supplied
309 if (!$target_language_code || !$translation_file_key) {
310 &throw_fatal_error("Missing command argument.");
311 }
312
313 # Get (and check) the translation configuration
314 my ($source_file, $target_file, $translation_file_type)
315 = &get_translation_configuration($target_language_code, $translation_file_key);
316
317 # Parse the source language and target language files
318 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
319 my @source_file_lines = &read_file_lines($source_file_path);
320 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
321
322 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
323 my @target_file_lines = &read_file_lines($target_file_path);
324 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
325
326 # Filter out any automatically translated chunks
327 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
328 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
329 delete $source_file_key_to_line_mapping{$chunk_key};
330 delete $target_file_key_to_line_mapping{$chunk_key};
331 }
332 }
333
334 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
335 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
336 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
337 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
338
339 # Determine the target file chunks requiring translation
340 my @target_file_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping);
341 &log_message("Number of target chunks requiring translation: " . scalar(@target_file_keys_requiring_translation));
342
343 # Determine the target file chunks requiring updating
344 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);
345 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);
346 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);
347 &log_message("Number of target chunks requiring updating: " . scalar(@target_file_keys_requiring_updating));
348
349 my $xml_response = &create_xml_response_for_chunks_requiring_work($translation_file_key, $target_file, scalar(keys(%source_file_key_to_text_mapping)), \@target_file_keys_requiring_translation, \@target_file_keys_requiring_updating, $num_chunks_to_return, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping, \%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping);
350
351 return $xml_response;
352}
353
354
355sub get_language_status
356{
357 # The code of the target language (ensure it is lowercase)
358 my $target_language_code = lc(shift(@_));
359
360 # Check that the necessary arguments were supplied
361 if (!$target_language_code) {
362 &throw_fatal_error("Missing command argument.");
363 }
364
365 # Form an XML response to the command
366 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
367 $xml_response .= "<GTIResponse>\n";
368 $xml_response .= " <LanguageStatus code=\"$target_language_code\">\n";
369
370 foreach my $translation_file (@$gti_translation_files) {
371 my ($num_source_chunks, $num_target_chunks, $num_chunks_requiring_translation, $num_chunks_requiring_updating) = 0;
372 my $target_file_name = "";
373
374 if ($translation_file->{'key'} eq "gs3interface") {
375 my (%source_file_key_to_text_mapping, %target_file_key_to_text_mapping, %source_file_key_to_last_update_date_mapping, %target_file_key_to_last_update_date_mapping ) = ();
376 &build_gs3_configuration($target_language_code, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping, \%source_file_key_to_last_update_date_mapping, \%target_file_key_to_last_update_date_mapping );
377
378 my @target_file_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping);
379 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);
380
381 $num_source_chunks = scalar(keys(%source_file_key_to_text_mapping));
382 $num_target_chunks = scalar(keys(%target_file_key_to_text_mapping));
383 $num_chunks_requiring_translation = scalar(@target_file_keys_requiring_translation);
384 $num_chunks_requiring_updating = scalar(@target_file_keys_requiring_updating);
385 }
386 else {
387 # Get (and check) the translation configuration
388 my ($source_file, $target_file, $translation_file_type) = &get_translation_configuration($target_language_code, $translation_file->{'key'});
389 $target_file_name = $target_file;
390
391 # Parse the source language and target language files
392 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
393 my @source_file_lines = &read_file_lines($source_file_path);
394 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
395
396 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
397 my @target_file_lines = &read_file_lines($target_file_path);
398 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
399
400 # Filter out any automatically translated chunks
401 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
402 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
403 delete $source_file_key_to_line_mapping{$chunk_key};
404 delete $target_file_key_to_line_mapping{$chunk_key};
405 }
406 }
407
408 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
409 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
410
411 # Determine the target file chunks requiring translation
412 my @target_file_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping);
413
414 # Determine the target file chunks requiring updating
415 my @target_file_keys_requiring_updating = ();
416 if (-e $target_file_path) {
417 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);
418 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);
419 @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);
420 }
421
422 $num_source_chunks = scalar(keys(%source_file_key_to_text_mapping));
423 $num_target_chunks = scalar(keys(%target_file_key_to_text_mapping));
424 $num_chunks_requiring_translation = scalar(@target_file_keys_requiring_translation);
425 $num_chunks_requiring_updating = scalar(@target_file_keys_requiring_updating);
426 }
427
428 &log_message("Status of " . $translation_file->{'key'});
429 &log_message("Number of source chunks: " . $num_source_chunks);
430 &log_message("Number of target chunks: " . $num_target_chunks);
431 &log_message("Number of target chunks requiring translation: " . $num_chunks_requiring_translation);
432 &log_message("Number of target chunks requiring updating: " . $num_chunks_requiring_updating);
433
434 $xml_response .= " <TranslationFile"
435 . " key=\"" . $translation_file->{'key'} . "\""
436 . " target_file_path=\"" . $target_file_name . "\""
437 . " num_chunks_translated=\"" . ($num_source_chunks - $num_chunks_requiring_translation) . "\""
438 . " num_chunks_requiring_translation=\"" . $num_chunks_requiring_translation . "\""
439 . " num_chunks_requiring_updating=\"" . $num_chunks_requiring_updating . "\"\/>\n";
440 }
441
442 $xml_response .= " </LanguageStatus>\n";
443
444 $xml_response .= "</GTIResponse>\n";
445 return $xml_response;
446}
447
448
449sub search_chunks
450{
451 # The code of the target language (ensure it is lowercase)
452 my $target_language_code = lc(shift(@_));
453 # The key of the file to translate (ensure it is lowercase)
454 my $translation_file_key = lc(shift(@_));
455 # The query string
456 my $query_string = join(' ', @_);
457
458 # Check that the necessary arguments were supplied
459 if (!$target_language_code || !$translation_file_key || !$query_string) {
460 &throw_fatal_error("Missing command argument.");
461 }
462
463 my ($source_file, $target_file, $translation_file_type) = ();
464 my %source_file_key_to_text_mapping = ();
465 my %target_file_key_to_text_mapping = ();
466
467
468 if ($translation_file_key ne "gs3interface") {
469 # Get (and check) the translation configuration
470 ($source_file, $target_file, $translation_file_type) = &get_translation_configuration($target_language_code, $translation_file_key);
471
472 # Parse the source language and target language files
473 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
474 my @source_file_lines = &read_file_lines($source_file_path);
475 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
476
477 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
478 my @target_file_lines = &read_file_lines($target_file_path);
479 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
480
481 # Filter out any automatically translated chunks
482 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
483 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
484 delete $source_file_key_to_line_mapping{$chunk_key};
485 delete $target_file_key_to_line_mapping{$chunk_key};
486 }
487 }
488
489 %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
490 %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
491 }
492 else {
493 # Not needed in this case
494 my (%source_file_key_to_gti_command_mapping, %target_file_key_to_gti_command_mapping) = ();
495 &build_gs3_configuration($target_language_code, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping,
496 \%source_file_key_to_gti_command_mapping, \%target_file_key_to_gti_command_mapping);
497 }
498
499 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
500 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
501
502 # Determine the target file chunks matching the query
503 my @target_file_keys_matching_query = ();
504 foreach my $chunk_key (keys(%target_file_key_to_text_mapping)) {
505 my $target_file_text = $target_file_key_to_text_mapping{$chunk_key};
506 if ($target_file_text =~ /$query_string/i) {
507 # &log_message("Chunk with key $chunk_key matches query.");
508 push(@target_file_keys_matching_query, $chunk_key);
509 }
510 }
511
512 # Form an XML response to the command
513 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
514 $xml_response .= "<GTIResponse>\n";
515
516 $xml_response .= " <ChunksMatchingQuery size=\"" . scalar(@target_file_keys_matching_query) . "\">\n";
517 foreach my $chunk_key (@target_file_keys_matching_query) {
518 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping{$chunk_key});
519
520 $xml_response .= " <Chunk key=\"$chunk_key\">\n";
521 $xml_response .= " <TargetFileText>$target_file_chunk_text</TargetFileText>\n";
522 $xml_response .= " </Chunk>\n";
523 }
524 $xml_response .= " </ChunksMatchingQuery>\n";
525
526 $xml_response .= "</GTIResponse>\n";
527 return $xml_response;
528}
529
530
531sub submit_translations
532{
533 # The code of the target language (ensure it is lowercase)
534 my $target_language_code = lc(shift(@_));
535 # The key of the file to translate (ensure it is lowercase)
536 my $translation_file_key = lc(shift(@_));
537 # The username of the translation submitter
538 my $submitter_username = shift(@_);
539 # Whether to submit a target chunk even if it hasn't changed
540 my $force_submission_flag = shift(@_);
541
542 # Check that the necessary arguments were supplied
543 if (!$target_language_code || !$translation_file_key || !$submitter_username) {
544 &log_message("Fatal error (but cannot be thrown): Missing command argument.");
545 die "\n";
546 }
547
548 my %source_file_key_to_text_mapping = ();
549 my %source_file_key_to_gti_comment_mapping = ();
550 my %target_file_key_to_text_mapping = ();
551 my %target_file_key_to_gti_comment_mapping = ();
552
553 my (@source_file_lines, @target_file_lines) = ();
554 my ($source_file, $target_file, $translation_file_type);
555
556
557 if ($translation_file_key ne "gs3interface") {
558 # Get (and check) the translation configuration
559 ($source_file, $target_file, $translation_file_type)
560 = &get_translation_configuration($target_language_code, $translation_file_key);
561
562 # Parse the source language and target language files
563 @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
564 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
565 %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
566 %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);
567
568 @target_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $target_file));
569 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
570 %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
571 %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);
572 }
573 else {
574 &build_gs3_configuration($target_language_code, \%source_file_key_to_text_mapping, \%target_file_key_to_text_mapping,
575 \%source_file_key_to_gti_comment_mapping, \%target_file_key_to_gti_comment_mapping);
576 }
577 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
578 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
579
580 # Submission date
581 my $day = (localtime)[3];
582 my $month = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")[(localtime)[4]];
583 my $year = (localtime)[5] + 1900;
584 my $submission_date = "$day-$month-$year";
585
586 open(SUBMISSION, "-");
587 my @submission_lines = <SUBMISSION>;
588 close(SUBMISSION);
589
590 # Remove any nasty carriage returns
591 # &log_message("Submission:");
592 foreach my $submission_line (@submission_lines) {
593 $submission_line =~ s/\r$//;
594 #&log_message(" $submission_line");
595 }
596
597 my %source_file_key_to_submission_mapping = ();
598 my %target_file_key_to_submission_mapping = ();
599 for (my $i = 0; $i < scalar(@submission_lines); $i++) {
600 # Read source file part of submission
601 if ($submission_lines[$i] =~ /^\<SourceFileText key=\"(.+)\"\>/) {
602 my $chunk_key = $1;
603
604 # Read the source file text
605 my $source_file_chunk_text = "";
606 $i++;
607 while ($i < scalar(@submission_lines) && $submission_lines[$i] !~ /^\<\/SourceFileText\>/) {
608 $source_file_chunk_text .= $submission_lines[$i];
609 $i++;
610 }
611 $source_file_chunk_text =~ s/\n$//; # Strip the extra newline character added
612 $source_file_chunk_text = &unmake_text_xml_safe($source_file_chunk_text);
613
614 #&log_message("Source file key: $chunk_key");
615 #&log_message("Source file text: $source_file_chunk_text");
616 $source_file_key_to_submission_mapping{$chunk_key} = $source_file_chunk_text;
617 }
618
619 # Read target file part of submission
620 if ($submission_lines[$i] =~ /^\<TargetFileText key=\"(.+)\"\>/) {
621 my $chunk_key = $1;
622
623 # Read the target file text
624 my $target_file_chunk_text = "";
625 $i++;
626 while ($i < scalar(@submission_lines) && $submission_lines[$i] !~ /^\<\/TargetFileText\>/) {
627 $target_file_chunk_text .= $submission_lines[$i];
628 $i++;
629 }
630 $target_file_chunk_text =~ s/\n$//; # Strip the extra newline character added
631 $target_file_chunk_text = &unmake_text_xml_safe($target_file_chunk_text);
632
633 #&log_message("Target file key: $chunk_key");
634 #&log_message("Target file text: $target_file_chunk_text");
635 $target_file_key_to_submission_mapping{$chunk_key} = $target_file_chunk_text;
636 }
637 }
638
639 # -----------------------------------------
640 # Validate the translation submissions
641 # -----------------------------------------
642
643 # Check that the translations are valid
644 foreach my $chunk_key (keys(%source_file_key_to_submission_mapping)) {
645 # Make sure the submitted chunk still exists in the source file
646 if (!defined($source_file_key_to_text_mapping{$chunk_key})) {
647 &log_message("Warning: Source chunk $chunk_key no longer exists (ignoring submission).");
648 delete $source_file_key_to_submission_mapping{$chunk_key};
649 delete $target_file_key_to_submission_mapping{$chunk_key};
650 next;
651 }
652
653 # Make sure the submitted source chunk matches the source file chunk
654 if ($source_file_key_to_submission_mapping{$chunk_key} ne &unmake_text_xml_safe($source_file_key_to_text_mapping{$chunk_key})) {
655 &log_message("Warning: Source chunk $chunk_key has changed (ignoring submission).");
656 &log_message("Submission source: $source_file_key_to_submission_mapping{$chunk_key}");
657 &log_message(" Source text: $source_file_key_to_text_mapping{$chunk_key}");
658 delete $source_file_key_to_submission_mapping{$chunk_key};
659 delete $target_file_key_to_submission_mapping{$chunk_key};
660 next;
661 }
662 }
663
664 # Apply the submitted translations
665 foreach my $chunk_key (keys(%target_file_key_to_submission_mapping)) {
666 # Only apply the submission if it is a change, unless -force_submission has been specified
667 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}) {
668 $target_file_key_to_text_mapping{$chunk_key} = $target_file_key_to_submission_mapping{$chunk_key};
669 $target_file_key_to_gti_comment_mapping{$chunk_key} = "Updated $submission_date by $submitter_username";
670 }
671 }
672
673 if ($translation_file_key ne "gs3interface") {
674 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)";
675 } else {
676 eval "&write_translated_gs3interface(\\\%source_file_key_to_text_mapping, \\\%target_file_key_to_text_mapping, \\\%target_file_key_to_gti_comment_mapping, \$target_language_code)";
677 }
678}
679
680
681sub create_glihelp_zip_file
682{
683 my $target_language_code = shift(@_);
684 my $translation_file_key = "glihelp";
685
686 &log_message("Creating GLI Help zip file for $target_language_code");
687
688 my ($source_file, $target_file, $translation_file_type) = &get_translation_data_for($target_language_code, $translation_file_key);
689
690 my $classpath = &util::filename_cat($gsdl_root_directory, "gti-lib");
691 if ( ! -e $classpath) {
692 &throw_fatal_error("$classpath doesn't exist! Need the files in this directory (ApplyXLST and its related files) to create the zip file for GLI Help");
693 }
694
695 my $gli_help_directory = &util::filename_cat($gsdl_root_directory, "gli");
696 $gli_help_directory = &util::filename_cat($gli_help_directory, "help");
697
698 my $gen_many_html_xsl_filepath = &util::filename_cat($gli_help_directory, "gen-many-html.xsl");
699 if ( ! -e $gen_many_html_xsl_filepath) {
700 &throw_fatal_error("$gen_many_html_xsl_filepath doesn't exist! Need this file to create the zip file for GLI Help");
701 }
702
703 my $gen_index_xml_xsl_filepath = &util::filename_cat($gli_help_directory, "gen-index-xml.xsl");
704 my $split_script_filepath = &util::filename_cat($gli_help_directory, "splithelpdocument.pl");
705
706 my $target_file_directory = &util::filename_cat($gli_help_directory, $target_language_code);
707 $target_file_directory = $target_file_directory."/";
708
709 my $target_filepath = &util::filename_cat($gsdl_root_directory, $target_file);
710
711 my $perl_exec = &util::get_perl_exec();
712 my $java_exec = "java";
713 if(defined($ENV{'JAVA_HOME'}) && $ENV{'JAVA_HOME'} ne ""){
714 $java_exec = &util::filename_cat($ENV{'JAVA_HOME'}, "bin", "java");
715 }
716
717 my $cmd = "$java_exec -cp $classpath:$classpath/xalan.jar ApplyXSLT $target_language_code $gen_many_html_xsl_filepath $target_filepath | \"$perl_exec\" -S $split_script_filepath $target_file_directory";
718 my $response = `$cmd`;
719
720 $cmd = "$java_exec -cp $classpath:$classpath/xalan.jar ApplyXSLT $target_language_code $gen_index_xml_xsl_filepath $target_filepath > " . $target_file_directory . "help_index.xml"; # 2>/dev/null";
721 $response = `$cmd`;
722
723 my $zip_file_path = "/greenstone/custom/gti/" . $target_language_code . "_GLIHelp.zip";
724 $cmd = "zip -rj $zip_file_path $target_file_directory -i \*.htm \*.xml";
725 $response = `$cmd`;
726}
727
728
729sub get_translation_configuration
730{
731 # Get the code of the target language
732 my $target_language_code = shift(@_);
733 # Get the key of the file to translate
734 my $translation_file_key = shift(@_);
735
736 # Read the translation data from the gti.cfg file
737 my ($source_file, $target_file, $translation_file_type) =
738 &get_translation_data_for($target_language_code, $translation_file_key);
739
740 # Check that the file to translate is defined in the gti.cfg file
741 if (!$source_file || !$target_file || !$translation_file_type) {
742 &throw_fatal_error("Missing or incomplete specification for translation file \"$translation_file_key\" in gti.pl.");
743 }
744
745 # Check that the source file exists
746 my $source_file_path = &util::filename_cat($gsdl_root_directory, $source_file);
747 if (!-e $source_file_path) {
748 &throw_fatal_error("Source file $source_file_path does not exist.");
749 }
750
751 # Check that the source file is up to date
752 # The "2>/dev/null" is very important! If it is missing this will never return when run from the receptionist
753 # unless ($translation_file_is_not_in_cvs) {
754 #my $source_file_cvs_status = `cd $gsdl_root_directory; cvs -d $anonymous_cvs_root update $source_file 2>/dev/null`;
755 my $source_file_cvs_status = `cd $gsdl_root_directory; svn status $source_file 2>/dev/null`;
756 if ($source_file_cvs_status =~ /^C /) {
757 &throw_fatal_error("Source file $source_file_path conflicts with the repository.");
758 }
759 if ($source_file_cvs_status =~ /^M /) {
760 &throw_fatal_error("Source file $source_file_path contains uncommitted changes.");
761 }
762 # }
763
764 return ($source_file, $target_file, $translation_file_type);
765}
766
767
768sub get_translation_data_for
769{
770 my ($target_language_code, $translation_file_key) = @_;
771
772 foreach my $translation_file (@$gti_translation_files) {
773 # If this isn't the correct translation file, move onto the next one
774 next if ($translation_file_key ne $translation_file->{'key'});
775
776 # Resolve the target language file
777 my $target_language_file = $translation_file->{'target_file'};
778 if ($target_language_file =~ /(\{.+\;.+\})/) {
779 my $unresolved_target_language_file_part = $1;
780
781 # Check for a special case for the target language code
782 if ($unresolved_target_language_file_part =~ /(\{|\;)$target_language_code:([^\;]+)(\;|\})/) {
783 my $resolved_target_language_file_part = $2;
784 $target_language_file =~ s/$unresolved_target_language_file_part/$resolved_target_language_file_part/;
785 }
786 # Otherwise use the last part as the default value
787 else {
788 my ($default_target_language_file_part) = $unresolved_target_language_file_part =~ /([^\;]+)\}/;
789 $target_language_file =~ s/$unresolved_target_language_file_part/\{$default_target_language_file_part\}/;
790 }
791 }
792
793 # Resolve instances of {iso_639_1_target_language_name}
794 my $iso_639_1_target_language_name = $iso639::fromiso639{$target_language_code};
795 $iso_639_1_target_language_name =~ tr/A-Z/a-z/ if $iso_639_1_target_language_name;
796 $target_language_file =~ s/\{iso_639_1_target_language_name\}/$iso_639_1_target_language_name/g;
797
798 # Resolve instances of {target_language_code}
799 $target_language_file =~ s/\{target_language_code\}/$target_language_code/g;
800
801 return ($translation_file->{'source_file'}, $target_language_file, $translation_file->{'file_type'});
802}
803
804return ();
805}
806
807
808sub read_file_lines
809{
810 my ($file_path) = @_;
811
812 if (!open(FILE_IN, "<$file_path")) {
813 &log_message("Note: Could not open file $file_path.");
814 return ();
815 }
816 my @file_lines = <FILE_IN>;
817 close(FILE_IN);
818
819 return @file_lines;
820}
821
822
823sub build_key_to_line_mapping
824{
825 my ($file_lines, $translation_file_type) = @_;
826 eval "return &build_key_to_line_mapping_for_${translation_file_type}(\@\$file_lines)";
827}
828
829
830sub build_key_to_text_mapping
831{
832 my ($file_lines, $key_to_line_mapping, $translation_file_type) = @_;
833
834 my %key_to_text_mapping = ();
835 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
836 my $chunk_starting_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[0];
837 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[1];
838
839 my $chunk_text = @$file_lines[$chunk_starting_line];
840 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
841 $chunk_text .= @$file_lines[$l];
842 }
843
844 # Map from chunk key to text
845 eval "\$key_to_text_mapping{\${chunk_key}} = &import_chunk_from_${translation_file_type}(\$chunk_text)";
846 }
847
848 return %key_to_text_mapping;
849}
850
851
852sub build_key_to_last_update_date_mapping
853{
854 my ($file, $file_lines, $key_to_line_mapping, $translation_file_type) = @_;
855
856 # If the files aren't in CVS then we can't tell anything about what needs updating
857 # return () if ($translation_file_is_not_in_cvs);
858
859 # Build a mapping from key to CVS date
860 # Need to be careful with this mapping because the chunk keys won't necessarily all be valid
861 my %key_to_cvs_date_mapping = &build_key_to_cvs_date_mapping($file, $translation_file_type);
862
863 # Build a mapping from key to comment date
864 my %key_to_gti_comment_mapping = &build_key_to_gti_comment_mapping($file_lines, $key_to_line_mapping, $translation_file_type);
865
866 # Build a mapping from key to last update date (the latter of the CVS date and comment date)
867 my %key_to_last_update_date_mapping = ();
868 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
869 # Use the CVS date as a starting point
870 my $chunk_cvs_date = $key_to_cvs_date_mapping{$chunk_key};
871 $key_to_last_update_date_mapping{$chunk_key} = $chunk_cvs_date;
872
873 # If a comment date exists and it is after the CVS date, use that instead
874 # need to convert the comment date format to SVN format
875 my $chunk_gti_comment = $key_to_gti_comment_mapping{$chunk_key};
876 if (defined($chunk_gti_comment) && $chunk_gti_comment =~ /(\d?\d-\D\D\D-\d\d\d\d)/) {
877 my $chunk_comment_date = $1;
878 if ((!defined($chunk_cvs_date) || &is_date_after($chunk_comment_date, $chunk_cvs_date))) {
879 $key_to_last_update_date_mapping{$chunk_key} = $chunk_comment_date;
880 }
881 }
882 }
883
884 return %key_to_last_update_date_mapping;
885}
886
887
888sub build_key_to_cvs_date_mapping
889{
890 my ($filename, $translation_file_type) = @_;
891
892 # Use SVN to annotate each line of the file with the date it was last edited
893 # The "2>/dev/null" is very important! If it is missing this will never return when run from the receptionist
894 my $cvs_annotated_file = `cd $gsdl_root_directory; svn annotate -v --force $filename 2>/dev/null`;
895
896 my @cvs_annotated_file_lines = split(/\n/, $cvs_annotated_file);
897
898 my @cvs_annotated_file_lines_date = ();
899 foreach my $cvs_annotated_file_line (@cvs_annotated_file_lines) {
900 # Extract the date from the SVN annotation at the front
901 # svn format : 2007-07-16
902 $cvs_annotated_file_line =~ s/^\s+\S+\s+\S+\s(\S+)//;
903
904 push(@cvs_annotated_file_lines_date, $1);
905
906 # trim extra date information in svn annotation format
907 # 15:42:49 +1200 (Wed, 21 Jun 2006)
908 $cvs_annotated_file_line =~ s/^\s+\S+\s\S+\s\((.+?)\)\s//;
909 }
910
911 # Build a key to line mapping for the CVS annotated file, for matching the chunk key to the CVS date
912 my %key_to_line_mapping = &build_key_to_line_mapping(\@cvs_annotated_file_lines, $translation_file_type);
913
914 my %key_to_cvs_date_mapping = ();
915 foreach my $chunk_key (keys(%key_to_line_mapping)) {
916 my $chunk_starting_line = (split(/-/, $key_to_line_mapping{$chunk_key}))[0];
917 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping{$chunk_key}))[1];
918
919 # Find the date this chunk was last edited, from the CVS annotation
920 my $chunk_date = $cvs_annotated_file_lines_date[$chunk_starting_line];
921 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
922 if (&is_date_after($cvs_annotated_file_lines_date[$l], $chunk_date)) {
923 # This part of the chunk has been updated more recently
924 $chunk_date = $cvs_annotated_file_lines_date[$l];
925
926 }
927 }
928
929 # Map from chunk key to CVS date
930 $key_to_cvs_date_mapping{$chunk_key} = $chunk_date;
931 }
932
933 return %key_to_cvs_date_mapping;
934}
935
936
937sub build_key_to_gti_comment_mapping
938{
939 my ($file_lines, $key_to_line_mapping, $translation_file_type) = @_;
940
941 my %key_to_gti_comment_mapping = ();
942 foreach my $chunk_key (keys(%$key_to_line_mapping)) {
943 my $chunk_starting_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[0];
944 my $chunk_finishing_line = (split(/-/, $key_to_line_mapping->{$chunk_key}))[1];
945
946 my $chunk_text = @$file_lines[$chunk_starting_line];
947 for (my $l = ($chunk_starting_line + 1); $l <= $chunk_finishing_line; $l++) {
948 $chunk_text .= @$file_lines[$l];
949 }
950
951 # Map from chunk key to GTI comment
952 my $chunk_gti_comment;
953 eval "\$chunk_gti_comment = &get_${translation_file_type}_chunk_gti_comment(\$chunk_text)";
954 $key_to_gti_comment_mapping{$chunk_key} = $chunk_gti_comment if (defined($chunk_gti_comment));
955 }
956
957 return %key_to_gti_comment_mapping;
958}
959
960
961sub determine_chunks_requiring_translation
962{
963 my $source_file_key_to_text_mapping = shift(@_);
964 my $target_file_key_to_text_mapping = shift(@_);
965
966 # Chunks needing translation are those in the source file with no translation in the target file
967 my @target_file_keys_requiring_translation = ();
968 foreach my $chunk_key (keys(%$source_file_key_to_text_mapping)) {
969 if ($source_file_key_to_text_mapping->{$chunk_key} && !$target_file_key_to_text_mapping->{$chunk_key}) {
970 # &log_message("Chunk with key $chunk_key needs translating.");
971 push(@target_file_keys_requiring_translation, $chunk_key);
972 }
973 }
974
975 return @target_file_keys_requiring_translation;
976}
977
978
979sub determine_chunks_requiring_updating
980{
981 my $source_file_key_to_last_update_date_mapping = shift(@_);
982 my $target_file_key_to_last_update_date_mapping = shift(@_);
983
984 # Chunks needing updating are those in the target file that have been more recently edited in the source file
985 my @target_file_keys_requiring_updating = ();
986 foreach my $chunk_key (keys(%$source_file_key_to_last_update_date_mapping)) {
987 my $source_chunk_last_update_date = $source_file_key_to_last_update_date_mapping->{$chunk_key};
988 my $target_chunk_last_update_date = $target_file_key_to_last_update_date_mapping->{$chunk_key};
989
990 # print "key: $chunk_key\nsource date : $source_chunk_last_update_date\ntarget date : $target_chunk_last_update_date\nafter? ". &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date) . "\n\n";
991
992 if (defined($target_chunk_last_update_date) && &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date)) {
993 # &log_message("Chunk with key $chunk_key needs updating.");
994 push(@target_file_keys_requiring_updating, $chunk_key);
995 }
996 }
997
998 return @target_file_keys_requiring_updating;
999}
1000
1001
1002sub is_chunk_automatically_translated
1003{
1004 my ($chunk_key, $translation_file_type) = @_;
1005 eval "return &is_${translation_file_type}_chunk_automatically_translated(\$chunk_key)";
1006}
1007
1008
1009sub make_text_xml_safe
1010{
1011 my $text = shift(@_);
1012 $text =~ s/\&/\&amp\;/g;
1013 $text =~ s/\&amp\;lt\;/\&amp\;amp\;lt\;/g;
1014 $text =~ s/\&amp\;gt\;/\&amp\;amp\;gt\;/g;
1015 $text =~ s/\&amp\;rarr\;/\&amp\;amp\;rarr\;/g;
1016 $text =~ s/\&amp\;mdash\;/\&amp\;amp\;mdash\;/g;
1017 $text =~ s/</\&lt\;/g;
1018 $text =~ s/>/\&gt\;/g;
1019 return $text;
1020}
1021
1022
1023sub unmake_text_xml_safe
1024{
1025 my $text = shift(@_);
1026 $text =~ s/\&lt\;/</g;
1027 $text =~ s/\&gt\;/>/g;
1028 $text =~ s/\&amp\;/\&/g;
1029 return $text;
1030}
1031
1032
1033# Returns 1 if $date1 is after $date2, 0 otherwise
1034sub is_date_after_cvs
1035{
1036 my ($date1, $date2) = @_;
1037 my %months = ("Jan", 1, "Feb", 2, "Mar", 3, "Apr", 4, "May", 5, "Jun", 6,
1038 "Jul", 7, "Aug", 8, "Sep", 9, "Oct", 10, "Nov", 11, "Dec", 12);
1039
1040 if(!defined $date1) {
1041 return 1;
1042 }
1043
1044 my @date1parts = split(/-/, $date1);
1045 my @date2parts = split(/-/, $date2);
1046
1047 # Compare year - nasty because we have rolled over into a new century
1048 my $year1 = $date1parts[2];
1049 if ($year1 < 80) {
1050 $year1 += 2000;
1051 }
1052 my $year2 = $date2parts[2];
1053 if ($year2 < 80) {
1054 $year2 += 2000;
1055 }
1056
1057 # Compare year
1058 if ($year1 > $year2) {
1059 return 1;
1060 }
1061 elsif ($year1 == $year2) {
1062 # Year is the same, so compare month
1063 if ($months{$date1parts[1]} > $months{$date2parts[1]}) {
1064 return 1;
1065 }
1066 elsif ($months{$date1parts[1]} == $months{$date2parts[1]}) {
1067 # Month is the same, so compare day
1068 if ($date1parts[0] > $date2parts[0]) {
1069 return 1;
1070 }
1071 }
1072 }
1073
1074 return 0;
1075}
1076
1077sub is_date_after
1078{
1079 my ($date1, $date2) = @_;
1080
1081 if(!defined $date1) {
1082 return 1;
1083 }
1084 if(!defined $date2) {
1085 return 0;
1086 }
1087
1088 # 16-Aug-2006
1089 if($date1=~ /(\d+?)-(\S\S\S)-(\d\d\d\d)/){
1090 my %months = ("Jan", "01", "Feb", "02", "Mar", "03", "Apr", "04", "May", "05", "Jun", "06",
1091 "Jul", "07", "Aug", "08", "Sep", "09", "Oct", "10", "Nov", "11", "Dec", "12");
1092 $date1=$3 . "-" . $months{$2} . "-" . $1;
1093 # print "** converted date1: $date1\n";
1094 }
1095 if($date2=~ /(\d+?)-(\S\S\S)-(\d\d\d\d)/){
1096 my %months = ("Jan", "01", "Feb", "02", "Mar", "03", "Apr", "04", "May", "05", "Jun", "06",
1097 "Jul", "07", "Aug", "08", "Sep", "09", "Oct", "10", "Nov", "11", "Dec", "12");
1098 $date2=$3 . "-" . $months{$2} . "-" . $1;
1099 # print "** converted date2: $date2\n";
1100 }
1101
1102
1103 # 2006-08-16
1104 my @date1parts = split(/-/, $date1);
1105 my @date2parts = split(/-/, $date2);
1106
1107 # Compare year
1108 if ($date1parts[0] > $date2parts[0]) {
1109 return 1;
1110 }
1111 elsif ($date1parts[0] == $date2parts[0]) {
1112 # Year is the same, so compare month
1113 if ($date1parts[1] > $date2parts[1]) {
1114 return 1;
1115 }
1116 elsif ($date1parts[1] == $date2parts[1]) {
1117 # Month is the same, so compare day
1118 if ($date1parts[2] > $date2parts[2]) {
1119 return 1;
1120 }
1121 }
1122 }
1123
1124 return 0;
1125}
1126
1127
1128sub create_xml_response_for_chunks_requiring_work
1129{
1130 my ($translation_file_key, $target_file, $total_num_chunks, $target_files_keys_requiring_translation, $target_files_keys_requiring_updating, $num_chunks_to_return, $source_files_key_to_text_mapping, $target_files_key_to_text_mapping, $source_files_key_to_last_update_date_mapping, $target_files_key_to_last_update_date_mapping) = @_;
1131
1132 # Form an XML response to the command
1133 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
1134 $xml_response .= "<GTIResponse>\n";
1135 $xml_response .= " <TranslationFile"
1136 . " key=\"" . $translation_file_key . "\""
1137 . " target_file_path=\"" . $target_file . "\""
1138 . " num_chunks_translated=\"" . ($total_num_chunks - scalar(@$target_files_keys_requiring_translation)) . "\""
1139 . " num_chunks_requiring_translation=\"" . scalar(@$target_files_keys_requiring_translation) . "\""
1140 . " num_chunks_requiring_updating=\"" . scalar(@$target_files_keys_requiring_updating) . "\"\/>\n";
1141
1142 # Do chunks requiring translation first
1143 if ($num_chunks_to_return > scalar(@$target_files_keys_requiring_translation)) {
1144 $xml_response .= " <ChunksRequiringTranslation size=\"" . scalar(@$target_files_keys_requiring_translation) . "\">\n";
1145 }
1146 else {
1147 $xml_response .= " <ChunksRequiringTranslation size=\"" . $num_chunks_to_return . "\">\n";
1148 }
1149
1150 my @sorted_chunk_keys = sort (@$target_files_keys_requiring_translation);
1151 foreach my $chunk_key (@sorted_chunk_keys) {
1152 last if ($num_chunks_to_return == 0);
1153
1154 my $source_file_chunk_date = $source_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1155 my $source_file_chunk_text = &make_text_xml_safe($source_files_key_to_text_mapping->{$chunk_key});
1156
1157 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1158 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1159 $xml_response .= " <TargetFileText></TargetFileText>\n";
1160 $xml_response .= " </Chunk>\n";
1161
1162 $num_chunks_to_return--;
1163 }
1164
1165 $xml_response .= " </ChunksRequiringTranslation>\n";
1166
1167 # Then do chunks requiring updating
1168 if ($num_chunks_to_return > scalar(@$target_files_keys_requiring_updating)) {
1169 $xml_response .= " <ChunksRequiringUpdating size=\"" . scalar(@$target_files_keys_requiring_updating) . "\">\n";
1170 }
1171 else {
1172 $xml_response .= " <ChunksRequiringUpdating size=\"" . $num_chunks_to_return . "\">\n";
1173 }
1174
1175 # foreach my $chunk_key (@target_file_keys_requiring_updating) {
1176 @sorted_chunk_keys = sort (@$target_files_keys_requiring_updating);
1177 foreach my $chunk_key (@sorted_chunk_keys) {
1178 last if ($num_chunks_to_return == 0);
1179
1180 my $source_file_chunk_date = $source_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1181 my $source_file_chunk_text = &make_text_xml_safe($source_files_key_to_text_mapping->{$chunk_key});
1182 my $target_file_chunk_date = $target_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1183 my $target_file_chunk_text = &make_text_xml_safe($target_files_key_to_text_mapping->{$chunk_key});
1184
1185 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1186 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1187 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
1188 $xml_response .= " </Chunk>\n";
1189
1190 $num_chunks_to_return--;
1191 }
1192
1193 $xml_response .= " </ChunksRequiringUpdating>\n";
1194
1195 $xml_response .= "</GTIResponse>\n";
1196
1197 return $xml_response;
1198}
1199
1200sub create_xml_response_for_uptodate_chunks
1201{
1202 my ($translation_file_key, $target_file, $uptodate_target_files_keys, $source_files_key_to_text_mapping, $target_files_key_to_text_mapping, $source_files_key_to_last_update_date_mapping, $target_files_key_to_last_update_date_mapping) = @_;
1203
1204 # Form an XML response to the command
1205 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
1206 $xml_response .= "<GTIResponse>\n";
1207 $xml_response .= " <TranslationFile"
1208 . " key=\"" . $translation_file_key . "\""
1209 . " target_file_path=\"" . $target_file . "\""
1210 . " num_chunks_uptodate=\"" . scalar(@$uptodate_target_files_keys) . "\"\/>\n";
1211
1212
1213 # Then do chunks requiring updating
1214 $xml_response .= " <UptodateChunks size=\"" . scalar(@$uptodate_target_files_keys) . "\">\n";
1215
1216
1217 # foreach my $chunk_key (@uptodate_target_file_keys) {
1218 my @sorted_chunk_keys = sort (@$uptodate_target_files_keys);
1219 foreach my $chunk_key (@sorted_chunk_keys) {
1220
1221 my $source_file_chunk_date = $source_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1222 my $source_file_chunk_text = &make_text_xml_safe($source_files_key_to_text_mapping->{$chunk_key});
1223 my $target_file_chunk_date = $target_files_key_to_last_update_date_mapping->{$chunk_key} || "";
1224 my $target_file_chunk_text = &make_text_xml_safe($target_files_key_to_text_mapping->{$chunk_key});
1225
1226 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1227 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1228 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
1229 $xml_response .= " </Chunk>\n";
1230
1231 }
1232
1233 $xml_response .= " </UptodateChunks>\n";
1234
1235 $xml_response .= "</GTIResponse>\n";
1236
1237 return $xml_response;
1238}
1239
1240sub create_xml_response_for_all_chunks
1241{
1242 my ($translation_file_key, $target_file, $source_file_key_to_text_mapping, $target_file_key_to_text_mapping, $source_file_key_to_last_update_date_mapping, $target_file_key_to_last_update_date_mapping) = @_;
1243
1244 # Form an XML response to the command
1245 my $xml_response = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
1246 $xml_response .= "<GTIResponse>\n";
1247 $xml_response .= " <TranslationFile"
1248 . " key=\"" . $translation_file_key . "\""
1249 . " target_file_path=\"" . $target_file . "\"\/>\n";
1250
1251 # Do all the chunks
1252 $xml_response .= " <Chunks size=\"" . scalar(keys(%$source_file_key_to_text_mapping)) . "\">\n";
1253
1254 my @sorted_chunk_keys = sort (keys(%$source_file_key_to_text_mapping));
1255 foreach my $chunk_key (@sorted_chunk_keys) {
1256 my $source_file_chunk_date = $source_file_key_to_last_update_date_mapping->{$chunk_key} || "";
1257 my $source_file_chunk_text = &make_text_xml_safe($source_file_key_to_text_mapping->{$chunk_key});
1258
1259 $xml_response .= " <Chunk key=\"" . &make_text_xml_safe($chunk_key) . "\">\n";
1260 $xml_response .= " <SourceFileText date=\"$source_file_chunk_date\">$source_file_chunk_text</SourceFileText>\n";
1261 if (defined($target_file_key_to_text_mapping->{$chunk_key})) {
1262 my $target_file_chunk_date = $target_file_key_to_last_update_date_mapping->{$chunk_key} || "";
1263 my $target_file_chunk_text = &make_text_xml_safe($target_file_key_to_text_mapping->{$chunk_key});
1264 $xml_response .= " <TargetFileText date=\"$target_file_chunk_date\">$target_file_chunk_text</TargetFileText>\n";
1265 }
1266 else {
1267 $xml_response .= " <TargetFileText></TargetFileText>\n";
1268 }
1269
1270 $xml_response .= " </Chunk>\n";
1271 }
1272 $xml_response .= " </Chunks>\n";
1273
1274 $xml_response .= "</GTIResponse>\n";
1275 return $xml_response;
1276}
1277
1278
1279
1280# ==========================================================================================
1281# MACROFILE FUNCTIONS
1282
1283sub build_key_to_line_mapping_for_macrofile
1284{
1285 my (@file_lines) = @_;
1286
1287 my $macro_package;
1288 my %chunk_key_to_line_mapping = ();
1289 # Process the contents of the file, line by line
1290 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1291 my $line = $file_lines[$i];
1292 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1293
1294 # Check if a new package is being defined
1295 if ($line =~ m/^package\s+(.+)/) {
1296 $macro_package = $1;
1297 }
1298
1299 # Line contains a macro name
1300 elsif ($line =~ m/^(_\w+_)/) {
1301 my $macro_key = $1;
1302 $line =~ s/\s*([^\\]\#[^\}]+)?$//; # Remove any comments and nasty whitespace
1303
1304 # While there is still text of the macro to go...
1305 my $startline = $i;
1306 while ($line !~ /\}$/) {
1307 $i++;
1308 if ($i == scalar(@file_lines)) {
1309 &throw_fatal_error("Could not find end of macro $macro_key.");
1310 }
1311 $line = $file_lines[$i];
1312 $line =~ s/\s*([^\\]\#[^\}]+)?$//; # Remove any comments and nasty whitespace
1313 }
1314
1315 # The chunk key consists of the package name and the macro key
1316 my $chunk_key = $macro_package . "." . $macro_key;
1317 # Map from chunk key to line
1318 $chunk_key_to_line_mapping{$chunk_key} = $startline . "-" . $i;
1319 }
1320
1321 # Icon: line in format ## "image text" ## image_type ## macro_name ##
1322 elsif ($line =~ m/^\#\# .* \#\# .* \#\# (.*) \#\#/) {
1323 # The chunk key consists of package name and macro key
1324 my $chunk_key = $macro_package . "." . $1;
1325 # Map from chunk key to line
1326 $chunk_key_to_line_mapping{$chunk_key} = $i . "-" . $i;
1327}
1328}
1329
1330return %chunk_key_to_line_mapping;
1331}
1332
1333
1334sub import_chunk_from_macrofile
1335{
1336 my ($chunk_text) = @_;
1337
1338 # Is this an icon macro??
1339 if ($chunk_text =~ /^\#\# (.*)/) {
1340 # Extract image macro text
1341 $chunk_text =~ /^\#\#\s+([^\#]+)\s+\#\#/;
1342 $chunk_text = $1;
1343
1344 # Remove enclosing quotes
1345 $chunk_text =~ s/^\"//;
1346 $chunk_text =~ s/\"$//;
1347}
1348
1349# No, so it must be a text macro
1350else {
1351 # Remove macro key
1352 $chunk_text =~ s/^_([^_]+)_(\s*)//;
1353
1354 # Remove language specifier
1355 $chunk_text =~ s/^\[l=.*\](\s*)//;
1356
1357 # Remove braces enclosing text
1358 $chunk_text =~ s/^{(\s*)((.|\n)*)}(\s*)(\#.+\s*)?/$2/;
1359}
1360
1361return $chunk_text;
1362}
1363
1364
1365sub get_macrofile_chunk_gti_comment
1366{
1367 my ($chunk_text) = @_;
1368
1369 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1370 if ($chunk_text =~ /\#\s+(Updated\s+\d?\d-\D\D\D-\d\d\d\d.*)\s*$/i) {
1371 return $1;
1372}
1373
1374return undef;
1375}
1376
1377
1378sub is_macrofile_chunk_automatically_translated
1379{
1380 my ($chunk_key) = @_;
1381
1382 # The _httpiconX_, _widthX_ and _heightX_ image macros are automatically translated
1383 if ($chunk_key =~ /\._(httpicon|width|height)/) {
1384 return 1;
1385 }
1386
1387 return 0;
1388}
1389
1390
1391# Use the source file to generate a target file that is formatted the same
1392sub write_translated_macrofile
1393{
1394 my $source_file = shift(@_); # Not used
1395 my @source_file_lines = @{shift(@_)};
1396 my $source_file_key_to_text_mapping = shift(@_);
1397 my $target_file = shift(@_);
1398 my @target_file_lines = @{shift(@_)};
1399 my $target_file_key_to_text_mapping = shift(@_);
1400 my $target_file_key_to_gti_comment_mapping = shift(@_);
1401 my $target_language_code = shift(@_);
1402
1403 # Build a mapping from source file line to chunk key
1404 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_macrofile(@source_file_lines);
1405 my %source_file_line_to_key_mapping = ();
1406 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1407 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1408 }
1409 my @source_file_line_keys = (sort sort_by_line (keys(%source_file_line_to_key_mapping)));
1410 my $source_file_line_number = 0;
1411
1412 # Build a mapping from target file line to chunk key
1413 my %target_file_key_to_line_mapping = &build_key_to_line_mapping_for_macrofile(@target_file_lines);
1414 my %target_file_line_to_key_mapping = ();
1415 foreach my $chunk_key (keys(%target_file_key_to_line_mapping)) {
1416 $target_file_line_to_key_mapping{$target_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1417 }
1418 my @target_file_line_keys = (sort sort_by_line (keys(%target_file_line_to_key_mapping)));
1419
1420 # Write the new target file
1421 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1422 if (!open(TARGET_FILE, ">$target_file_path")) {
1423 &throw_fatal_error("Could not write target file $target_file_path.");
1424 }
1425
1426 # Use the header from the target file, to keep language and author information
1427 if (scalar(@target_file_line_keys) > 0) {
1428 my $target_file_line_number = 0;
1429 my $target_file_chunk_starting_line_number = (split(/-/, $target_file_line_keys[0]))[0];
1430 while ($target_file_line_number < $target_file_chunk_starting_line_number) {
1431 my $target_file_line = $target_file_lines[$target_file_line_number];
1432 last if ($target_file_line =~ /^\# -- Missing translation: /); # We don't want to get into the macros
1433 print TARGET_FILE $target_file_line;
1434 $target_file_line_number++;
1435 }
1436
1437 $source_file_line_number = (split(/-/, $source_file_line_keys[0]))[0];
1438 }
1439
1440 # Model the new target file on the source file, with the target file translations
1441 foreach my $line_key (@source_file_line_keys) {
1442 # Fill in the gaps before this chunk starts
1443 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1444 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1445 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1446 print TARGET_FILE $source_file_lines[$source_file_line_number];
1447 $source_file_line_number++;
1448 }
1449 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1450
1451 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1452 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1453 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1454
1455 my $macrofile_key = $chunk_key;
1456 $macrofile_key =~ s/^(.+?)\.//;
1457
1458 # If no translation exists for this chunk, show this, and move on
1459 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1460 print TARGET_FILE "# -- Missing translation: $macrofile_key\n";
1461 next;
1462 }
1463
1464 # Grab the source chunk text
1465 my $source_file_chunk = $source_file_lines[$source_file_chunk_starting_line_number];
1466 for (my $l = ($source_file_chunk_starting_line_number + 1); $l <= $source_file_chunk_finishing_line_number; $l++) {
1467 $source_file_chunk .= $source_file_lines[$l];
1468 }
1469
1470 # Is this an icon macro??
1471 if ($source_file_chunk =~ /^\#\# (.*)/) {
1472 # Escape any newline and question mark characters so the source text is replaced correctly
1473 $source_file_chunk_text =~ s/\\/\\\\/g;
1474 $source_file_chunk_text =~ s/\?/\\\?/g;
1475
1476 # Build the new target chunk from the source chunk
1477 my $target_file_chunk = $source_file_chunk;
1478 $target_file_chunk =~ s/$source_file_chunk_text/$target_file_chunk_text/;
1479 $target_file_chunk =~ s/(\s)*$//;
1480 print TARGET_FILE "$target_file_chunk";
1481 }
1482
1483 # No, it is just a normal text macro
1484 else {
1485 print TARGET_FILE "$macrofile_key [l=$target_language_code] {$target_file_chunk_text}";
1486 }
1487
1488 # Add the "updated" comment, if one exists
1489 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1490 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$chunk_key};
1491 }
1492 print TARGET_FILE "\n";
1493}
1494
1495close(TARGET_FILE);
1496}
1497
1498
1499sub sort_by_line
1500{
1501 return ((split(/-/, $a))[0] <=> (split(/-/, $b))[0]);
1502}
1503
1504
1505# ==========================================================================================
1506# RESOURCE BUNDLE FUNCTIONS
1507
1508sub build_key_to_line_mapping_for_resource_bundle
1509{
1510 my (@file_lines) = @_;
1511
1512 my %chunk_key_to_line_mapping = ();
1513 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1514 my $line = $file_lines[$i];
1515 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1516
1517 # Line contains a dictionary string
1518 if ($line =~ /^(\S+?)[:|=](.*)$/) {
1519 my $chunk_key = $1;
1520
1521 # Map from chunk key to line
1522 $chunk_key_to_line_mapping{$chunk_key} = $i . "-" . $i;
1523 }
1524 }
1525
1526 return %chunk_key_to_line_mapping;
1527}
1528
1529
1530sub import_chunk_from_resource_bundle
1531{
1532 my ($chunk_text) = @_;
1533
1534 # Simple: just remove string key
1535 $chunk_text =~ s/^(\S+?)[:|=](\s*)//;
1536 $chunk_text =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1537 $chunk_text =~ s/(\s*)\#\s+Updated\s+(\d?\d-\D\D\D-\d\d\d\d.*)\s*$//i;
1538
1539 return $chunk_text;
1540}
1541
1542
1543sub get_resource_bundle_chunk_gti_comment
1544{
1545 my ($chunk_text) = @_;
1546
1547 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1548 if ($chunk_text =~ /\#\s+(Updated\s+\d?\d-\D\D\D-\d\d\d\d.*)\s*$/i) {
1549 return $1;
1550}
1551
1552return undef;
1553}
1554
1555
1556sub is_resource_bundle_chunk_automatically_translated
1557{
1558 # No resource bundle chunks are automatically translated
1559 return 0;
1560}
1561
1562
1563sub write_translated_resource_bundle
1564{
1565 my $source_file = shift(@_); # Not used
1566 my @source_file_lines = @{shift(@_)};
1567 my $source_file_key_to_text_mapping = shift(@_);
1568 my $target_file = shift(@_);
1569 my @target_file_lines = @{shift(@_)}; # Not used
1570 my $target_file_key_to_text_mapping = shift(@_);
1571 my $target_file_key_to_gti_comment_mapping = shift(@_);
1572 my $target_language_code = shift(@_); # Not used
1573
1574 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1575 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_resource_bundle(@source_file_lines);
1576 my %source_file_line_to_key_mapping = ();
1577 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1578 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1579 }
1580
1581 # Write the new target file
1582 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1583 if (!open(TARGET_FILE, ">$target_file_path")) {
1584 &throw_fatal_error("Could not write target file $target_file_path.");
1585 }
1586
1587 # Model the new target file on the source file, with the target file translations
1588 my $source_file_line_number = 0;
1589 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1590 # Fill in the gaps before this chunk starts
1591 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1592 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1593 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1594 print TARGET_FILE $source_file_lines[$source_file_line_number];
1595 $source_file_line_number++;
1596 }
1597 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1598
1599 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1600 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1601 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1602
1603 # If no translation exists for this chunk, show this, and move on
1604 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1605 print TARGET_FILE "# -- Missing translation: $chunk_key\n";
1606 next;
1607 }
1608
1609 print TARGET_FILE "$chunk_key:$target_file_chunk_text";
1610 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1611 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$chunk_key};
1612 }
1613 print TARGET_FILE "\n";
1614 }
1615
1616 close(TARGET_FILE);
1617}
1618
1619
1620# ==========================================================================================
1621# GREENSTONE XML FUNCTIONS
1622
1623sub build_key_to_line_mapping_for_greenstone_xml
1624{
1625 my (@file_lines) = @_;
1626
1627 my %chunk_key_to_line_mapping = ();
1628 for (my $i = 0; $i < scalar(@file_lines); $i++) {
1629 my $line = $file_lines[$i];
1630 $line =~ s/(\s*)$//; # Remove any nasty whitespace, carriage returns etc.
1631
1632 # Line contains a string to translate
1633 if ($line =~ /^\s*<Text id=\"(.*?)\">/) {
1634 my $chunk_key = $1;
1635 $line =~ s/\s*$//; # Remove any nasty whitespace
1636 $line =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1637
1638 # While there is still text of the string to go...
1639 my $startline = $i;
1640 while ($line !~ /<\/Text>$/) {
1641 $i++;
1642 if ($i == scalar(@file_lines)) {
1643 &throw_fatal_error("Could not find end of string $chunk_key.");
1644 }
1645 $line = $file_lines[$i];
1646 $line =~ s/\s*$//; # Remove any nasty whitespace
1647 $line =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1648 }
1649
1650 # Map from chunk key to line
1651 if (!defined($chunk_key_to_line_mapping{$chunk_key})) {
1652 $chunk_key_to_line_mapping{$chunk_key} = $startline . "-" . $i;
1653 }
1654 else {
1655 &throw_fatal_error("Duplicate key $chunk_key.");
1656 }
1657 }
1658 }
1659
1660 return %chunk_key_to_line_mapping;
1661}
1662
1663
1664sub import_chunk_from_greenstone_xml
1665{
1666 my ($chunk_text) = @_;
1667
1668 # Simple: just remove the Text tags
1669 $chunk_text =~ s/^\s*<Text id=\"(.*?)\">(\s*)//;
1670 $chunk_text =~ s/<Updated date=\"\d?\d-\D\D\D-\d\d\d\d.*\"\/>$//;
1671 $chunk_text =~ s/<\/Text>$//;
1672
1673 return $chunk_text;
1674}
1675
1676
1677sub get_greenstone_xml_chunk_gti_comment
1678{
1679 my ($chunk_text) = @_;
1680
1681 # Check for an "Updated DD-MMM-YYYY" comment at the end of the chunk
1682 if ($chunk_text =~ /<Updated date=\"(\d?\d-\D\D\D-\d\d\d\d.*)\"\/>$/i) {
1683 return $1;
1684 }
1685
1686 return undef;
1687}
1688
1689
1690sub is_greenstone_xml_chunk_automatically_translated
1691{
1692 # No greenstone XML chunks are automatically translated
1693 return 0;
1694}
1695
1696
1697sub write_translated_greenstone_xml
1698{
1699 my $source_file = shift(@_); # Not used
1700 my @source_file_lines = @{shift(@_)};
1701 my $source_file_key_to_text_mapping = shift(@_);
1702 my $target_file = shift(@_);
1703 my @target_file_lines = @{shift(@_)}; # Not used
1704 my $target_file_key_to_text_mapping = shift(@_);
1705 my $target_file_key_to_gti_comment_mapping = shift(@_);
1706 my $target_language_code = shift(@_); # Not used
1707
1708 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1709 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_greenstone_xml(@source_file_lines);
1710 my %source_file_line_to_key_mapping = ();
1711 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1712 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1713 }
1714
1715 # Write the new target file
1716 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1717 if (!open(TARGET_FILE, ">$target_file_path")) {
1718 &throw_fatal_error("Could not write target file $target_file_path.");
1719 }
1720
1721 # Model the new target file on the source file, with the target file translations
1722 my $source_file_line_number = 0;
1723 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1724 # Fill in the gaps before this chunk starts
1725 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1726 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1727 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1728 print TARGET_FILE $source_file_lines[$source_file_line_number];
1729 $source_file_line_number++;
1730 }
1731 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1732
1733 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1734 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$chunk_key};
1735 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$chunk_key} || "";
1736 $target_file_chunk_text =~ s/(\n)*$//g;
1737
1738 # If no translation exists for this chunk, show this, and move on
1739 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1740 print TARGET_FILE "<!-- Missing translation: $chunk_key -->\n";
1741 next;
1742 }
1743
1744 print TARGET_FILE "<Text id=\"$chunk_key\">$target_file_chunk_text</Text>";
1745 if ($target_file_key_to_gti_comment_mapping->{$chunk_key}) {
1746 my $chunk_gti_comment = $target_file_key_to_gti_comment_mapping->{$chunk_key};
1747 $chunk_gti_comment =~ s/^Updated //;
1748 print TARGET_FILE "<Updated date=\"" . $chunk_gti_comment . "\"\/>";
1749 }
1750 print TARGET_FILE "\n";
1751 }
1752
1753 # Fill in the end of the file
1754 while ($source_file_line_number < scalar(@source_file_lines)) {
1755 print TARGET_FILE $source_file_lines[$source_file_line_number];
1756 $source_file_line_number++;
1757 }
1758
1759 close(TARGET_FILE);
1760}
1761
1762
1763# ==========================================================================================
1764# GREENSTONE3 FUNCTIONS
1765
1766sub get_all_chunks_for_gs3
1767{
1768 # The code of the target language (ensure it is lowercase)
1769 my $target_language_code = lc(shift(@_));
1770 my $translation_file_key = lc(shift(@_));
1771
1772 # Check that the necessary arguments were supplied
1773 if (!$target_language_code) {
1774 &throw_fatal_error("Missing command argument.");
1775 }
1776
1777 # Get (and check) the translation configuration
1778 # my ($source_file_dir, $target_file, $translation_file_type) = &get_translation_configuration($target_language_code, $translation_file_key);
1779
1780 my %source_files_key_to_text_mapping = ();
1781 my %target_files_key_to_text_mapping = ();
1782 my %source_files_key_to_last_update_date_mapping = ();
1783 my %target_files_key_to_last_update_date_mapping = ();
1784
1785 &build_gs3_configuration($target_language_code, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping, \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1786
1787 &log_message("Total number of source chunks: " . scalar(keys(%source_files_key_to_text_mapping)));
1788 &log_message("Total number of target chunks: " . scalar(keys(%target_files_key_to_text_mapping)));
1789
1790 my $xml_response = &create_xml_response_for_all_chunks($translation_file_key, "", \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping, \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1791 return $xml_response;
1792}
1793
1794
1795sub get_first_n_chunks_requiring_work_for_gs3
1796{
1797 # The code of the target language (ensure it is lowercase)
1798 my $target_language_code = lc(shift(@_));
1799 # The key of the file to translate (ensure it is lowercase)
1800 my $translation_file_key = lc(shift(@_));
1801 # The number of chunks to return (defaults to one if not specified)
1802 my $num_chunks_to_return = shift(@_) || "1";
1803
1804 # Check that the necessary arguments were supplied
1805 if (!$target_language_code || !$translation_file_key) {
1806 &throw_fatal_error("Missing command argument.");
1807 }
1808
1809 my %source_files_key_to_text_mapping = ();
1810 my %target_files_key_to_text_mapping = ();
1811 my %source_files_key_to_last_update_date_mapping = ();
1812 my %target_files_key_to_last_update_date_mapping = ();
1813
1814 &build_gs3_configuration($target_language_code, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping,
1815 \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1816
1817 # Determine the target file chunks requiring translation
1818 my @target_files_keys_requiring_translation = &determine_chunks_requiring_translation(\%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping);
1819 # Determine the target file chunks requiring updating
1820 my @target_files_keys_requiring_updating = &determine_chunks_requiring_updating(\%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1821 &log_message("Total number of target chunks requiring translation: " . scalar(@target_files_keys_requiring_translation));
1822 &log_message("Total number of target chunks requiring updating: " . scalar(@target_files_keys_requiring_updating));
1823
1824 my $xml_response = &create_xml_response_for_chunks_requiring_work($translation_file_key, "", scalar(keys(%source_files_key_to_text_mapping)),
1825 \@target_files_keys_requiring_translation, \@target_files_keys_requiring_updating,
1826 $num_chunks_to_return, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping,
1827 \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1828
1829 return $xml_response;
1830}
1831
1832sub get_uptodate_chunks_for_gs3
1833{
1834 # The code of the target language (ensure it is lowercase)
1835 my $target_language_code = lc(shift(@_));
1836 # The key of the file to translate (ensure it is lowercase)
1837 my $translation_file_key = lc(shift(@_));
1838 # The number of chunks to return (defaults to one if not specified)
1839 my $num_chunks_to_return = shift(@_) || "1";
1840
1841 # Check that the necessary arguments were supplied
1842 if (!$target_language_code || !$translation_file_key) {
1843 &throw_fatal_error("Missing command argument.");
1844 }
1845
1846 my %source_files_key_to_text_mapping = ();
1847 my %target_files_key_to_text_mapping = ();
1848 my %source_files_key_to_last_update_date_mapping = ();
1849 my %target_files_key_to_last_update_date_mapping = ();
1850
1851 &build_gs3_configuration($target_language_code, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping,
1852 \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1853
1854
1855 # Chunks needing updating are those in the target file that have been more recently edited in the source file
1856 # All others are uptodate (which implies that they have certainly been translated at some point and would not be empty)
1857 my @uptodate_target_file_keys = ();
1858 foreach my $chunk_key (keys(%source_files_key_to_last_update_date_mapping)) {
1859 my $source_chunk_last_update_date = $source_files_key_to_last_update_date_mapping{$chunk_key};
1860 my $target_chunk_last_update_date = $target_files_key_to_last_update_date_mapping{$chunk_key};
1861
1862 # print "key: $chunk_key\nsource date : $source_chunk_last_update_date\ntarget date : $target_chunk_last_update_date\nafter? ". &is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date) . "\n\n";
1863
1864 if (defined($target_chunk_last_update_date) && !&is_date_after($source_chunk_last_update_date, $target_chunk_last_update_date)) {
1865 # &log_message("Chunk with key $chunk_key needs updating.");
1866 push(@uptodate_target_file_keys, $chunk_key);
1867 }
1868 }
1869
1870 my $xml_response = &create_xml_response_for_uptodate_chunks($translation_file_key, "", \@uptodate_target_file_keys, \%source_files_key_to_text_mapping, \%target_files_key_to_text_mapping, \%source_files_key_to_last_update_date_mapping, \%target_files_key_to_last_update_date_mapping);
1871
1872 return $xml_response;
1873}
1874
1875
1876sub build_gs3_configuration
1877{
1878 my ($target_language_code, $source_files_key_to_text_mapping, $target_files_key_to_text_mapping,
1879 $source_files_key_to_gti_comment_mapping, $target_files_key_to_gti_comment_mapping) = @_;
1880
1881 my $source_file_directory = "greenstone3";
1882 my $translation_file_type = "resource_bundle";
1883
1884 foreach my $interface_file_key (@gs3_interface_files) {
1885
1886 &log_message("Greenstone 3 interface file: " . $interface_file_key);
1887
1888 # Parse the source language and target language files
1889 my $source_file = &util::filename_cat($source_file_directory, $interface_file_key.".properties");
1890 my @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
1891 my %source_file_key_to_line_mapping = &build_key_to_line_mapping(\@source_file_lines, $translation_file_type);
1892 my %source_file_key_to_text_mapping = &build_key_to_text_mapping(\@source_file_lines, \%source_file_key_to_line_mapping, $translation_file_type);
1893 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);
1894
1895 my $target_file = &util::filename_cat($source_file_directory, $interface_file_key."_".$target_language_code.".properties");
1896 my @target_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $target_file));
1897 my %target_file_key_to_line_mapping = &build_key_to_line_mapping(\@target_file_lines, $translation_file_type);
1898 my %target_file_key_to_text_mapping = &build_key_to_text_mapping(\@target_file_lines, \%target_file_key_to_line_mapping, $translation_file_type);
1899 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);
1900
1901
1902 # Filter out any automatically translated chunks
1903 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1904 if (&is_chunk_automatically_translated($chunk_key, $translation_file_type)) {
1905 delete $source_file_key_to_line_mapping{$chunk_key};
1906 delete $target_file_key_to_line_mapping{$chunk_key};
1907 }
1908 }
1909
1910 &log_message("Number of source chunks: " . scalar(keys(%source_file_key_to_text_mapping)));
1911 &log_message("Number of target chunks: " . scalar(keys(%target_file_key_to_text_mapping)));
1912
1913 foreach my $chunk_key (keys(%source_file_key_to_text_mapping)) {
1914 my $global_chunk_key = "$interface_file_key.$chunk_key";
1915 $source_files_key_to_text_mapping->{$global_chunk_key} = $source_file_key_to_text_mapping{$chunk_key};
1916 $source_files_key_to_gti_comment_mapping->{$global_chunk_key} = $source_file_key_to_gti_comment_mapping{$chunk_key};
1917
1918 if (defined $target_file_key_to_text_mapping{$chunk_key}) {
1919 $target_files_key_to_text_mapping->{$global_chunk_key} = $target_file_key_to_text_mapping{$chunk_key};
1920 $target_files_key_to_gti_comment_mapping->{$global_chunk_key} = $target_file_key_to_gti_comment_mapping{$chunk_key};
1921 }
1922 }
1923 }
1924}
1925
1926
1927sub write_translated_gs3interface
1928{
1929 my $source_file_key_to_text_mapping = shift(@_);
1930 my $target_file_key_to_text_mapping = shift(@_);
1931 my $target_file_key_to_gti_comment_mapping = shift(@_);
1932 my $target_language_code = shift(@_);
1933
1934 my @sorted_chunk_keys = sort (keys(%$source_file_key_to_text_mapping));
1935
1936 my %translated_interface_file_keys = ();
1937 foreach my $chunk_key (keys(%$target_file_key_to_text_mapping)) {
1938 $chunk_key =~ /^([^\.]+)?\.(.*)$/;
1939 if (!defined $translated_interface_file_keys{$1}) {
1940 &log_message("Updated interface file: " . $1);
1941 $translated_interface_file_keys{$1}="";
1942 }
1943 }
1944 &log_message("Updated interface files: " . scalar(keys(%translated_interface_file_keys)));
1945
1946 my $source_file_directory = "greenstone3";
1947
1948 foreach my $interface_file_key (keys(%translated_interface_file_keys)) {
1949
1950 # Build a mapping from chunk key to source file line, and from source file line to chunk key
1951 my $source_file = &util::filename_cat($source_file_directory, "$interface_file_key.properties");
1952 my @source_file_lines = &read_file_lines(&util::filename_cat($gsdl_root_directory, $source_file));
1953 my %source_file_key_to_line_mapping = &build_key_to_line_mapping_for_resource_bundle(@source_file_lines);
1954 my %source_file_line_to_key_mapping = ();
1955 foreach my $chunk_key (keys(%source_file_key_to_line_mapping)) {
1956 $source_file_line_to_key_mapping{$source_file_key_to_line_mapping{$chunk_key}} = $chunk_key;
1957 }
1958
1959 # Write the new target file
1960 my $target_file = &util::filename_cat($source_file_directory, $interface_file_key . "_" . $target_language_code . ".properties");
1961 my $target_file_path = &util::filename_cat($gsdl_root_directory, $target_file);
1962 if (!open(TARGET_FILE, ">$target_file_path")) {
1963 &throw_fatal_error("Could not write target file $target_file_path.");
1964 }
1965
1966 # Model the new target file on the source file, with the target file translations
1967 my $source_file_line_number = 0;
1968 foreach my $line_key (sort sort_by_line (keys(%source_file_line_to_key_mapping))) {
1969 # Fill in the gaps before this chunk starts
1970 my $source_file_chunk_starting_line_number = (split(/-/, $line_key))[0];
1971 my $source_file_chunk_finishing_line_number = (split(/-/, $line_key))[1];
1972 while ($source_file_line_number < $source_file_chunk_starting_line_number) {
1973 print TARGET_FILE $source_file_lines[$source_file_line_number];
1974 $source_file_line_number++;
1975 }
1976 $source_file_line_number = $source_file_chunk_finishing_line_number + 1;
1977
1978 my $chunk_key = $source_file_line_to_key_mapping{$line_key};
1979 my $global_chunk_key = "$interface_file_key.$chunk_key";
1980 my $source_file_chunk_text = $source_file_key_to_text_mapping->{$global_chunk_key};
1981 my $target_file_chunk_text = $target_file_key_to_text_mapping->{$global_chunk_key} || "";
1982
1983 # If no translation exists for this chunk, show this, and move on
1984 if ($source_file_chunk_text ne "" && $target_file_chunk_text eq "") {
1985 print TARGET_FILE "# -- Missing translation: $chunk_key\n";
1986 next;
1987 }
1988
1989 print TARGET_FILE "$chunk_key:$target_file_chunk_text";
1990 if ($target_file_key_to_gti_comment_mapping->{$global_chunk_key}) {
1991 print TARGET_FILE " # " . $target_file_key_to_gti_comment_mapping->{$global_chunk_key};
1992 }
1993 print TARGET_FILE "\n";
1994 }
1995
1996 close(TARGET_FILE);
1997 }
1998}
1999
2000&main(@ARGV);
Note: See TracBrowser for help on using the repository browser.