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

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

Added code to allow us to get all the up to date strings, instead of just all the strings (but unsorted), or just the strings that require translating/updating. We can create translation memory files (TMX) from all the up to date strings so that can translators using the Google Translator Toolkit can use the strings and string segments as a reference while translating the strings that still require updating/translating. If the TMX file is shared in the GTT, these strings and their segments will also be available to help translators of other application interfaces to use common and consistent terminology.

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